Skip to content

Conversation

@marcoroth
Copy link
Owner

@marcoroth marcoroth commented Nov 2, 2025

This pull request adds Rust bindings for Herb using FFI (Foreign Function Interface) via bindgen, allowing Rust applications to parse and analyze ERB templates with the same implementation as the native C, Ruby C-Extension, C++ Emscripten WASM, Java JNI, and the C++ Node.js NAPI bindings.

Building the Rust bindings is requires a Rust toolchain, Bundler, Prism, and a C compiler:

cd rust
make all

Once built, you can use the herb-rust CLI tool:

herb-rust version

herb rust v0.7.5, libprism v1.6.0, libherb v0.7.5 (Rust FFI)

herb-rust lex examples/test.html.erb

#<Herb::Token type="TOKEN_HTML_TAG_START" value="<" range=[0, 1] start=(1:0) end=(1:1)>
#<Herb::Token type="TOKEN_IDENTIFIER" value="h1" range=[1, 3] start=(1:1) end=(1:3)>
#<Herb::Token type="TOKEN_WHITESPACE" value=" " range=[3, 4] start=(1:3) end=(1:4)>
#<Herb::Token type="TOKEN_IDENTIFIER" value="class" range=[4, 9] start=(1:4) end=(1:9)>
#<Herb::Token type="TOKEN_EQUALS" value="=" range=[9, 10] start=(1:9) end=(1:10)>
#<Herb::Token type="TOKEN_QUOTE" value="\"" range=[10, 11] start=(1:10) end=(1:11)>
#<Herb::Token type="TOKEN_IDENTIFIER" value="title" range=[11, 16] start=(1:11) end=(1:16)>
#<Herb::Token type="TOKEN_QUOTE" value="\"" range=[16, 17] start=(1:16) end=(1:17)>
#<Herb::Token type="TOKEN_HTML_TAG_END" value=">" range=[17, 18] start=(1:17) end=(1:18)>
#<Herb::Token type="TOKEN_ERB_START" value="<%=" range=[18, 21] start=(1:18) end=(1:21)>
#<Herb::Token type="TOKEN_ERB_CONTENT" value=" content " range=[21, 30] start=(1:21) end=(1:30)>
#<Herb::Token type="TOKEN_ERB_END" value="%>" range=[30, 32] start=(1:30) end=(1:32)>
#<Herb::Token type="TOKEN_HTML_TAG_START_CLOSE" value="</" range=[32, 34] start=(1:32) end=(1:34)>
#<Herb::Token type="TOKEN_IDENTIFIER" value="h1" range=[34, 36] start=(1:34) end=(1:36)>
#<Herb::Token type="TOKEN_HTML_TAG_END" value=">" range=[36, 37] start=(1:36) end=(1:37)>
#<Herb::Token type="TOKEN_NEWLINE" value="\n" range=[37, 38] start=(1:37) end=(2:0)>
#<Herb::Token type="TOKEN_EOF" value="<EOF>" range=[38, 38] start=(2:0) end=(2:0)>

herb-rust parse examples/test.html.erb

@ DocumentNode (location: (1:0)-(2:0))
└── children: (2 items)
    ├── @ HTMLElementNode (location: (1:0)-(1:37))
       ├── open_tag: 
          └── @ HTMLOpenTagNode (location: (1:0)-(1:18))
              ├── tag_opening: "<" (location: (1:0)-(1:1))
              ├── tag_name: "h1" (location: (1:1)-(1:3))
              ├── tag_closing: ">" (location: (1:17)-(1:18))
              ├── children: (1 item)
                 └── @ HTMLAttributeNode (location: (1:4)-(1:17))
                     ├── name: 
                        └── @ HTMLAttributeNameNode (location: (1:4)-(1:9))
                            └── children: (1 item)
                                └── @ LiteralNode (location: (1:4)-(1:9))
                                    └── content: "class"
                        
                     ├── equals: "=" (location: (1:9)-(1:10))
                     └── value: 
                         └── @ HTMLAttributeValueNode (location: (1:10)-(1:17))
                             ├── open_quote: "\"" (location: (1:10)-(1:11))
                             ├── children: (1 item)
                                └── @ LiteralNode (location: (1:11)-(1:16))
                                    └── content: "title"
                             ├── close_quote: "\"" (location: (1:16)-(1:17))
                             └── quoted: true
              └── is_void: false
          
       ├── tag_name: "h1" (location: (1:1)-(1:3))├── body: (1 item)
          └── @ ERBContentNode (location: (1:18)-(1:32))
              ├── tag_opening: "<%=" (location: (1:18)-(1:21))
              ├── content: " content " (location: (1:21)-(1:30))
              ├── tag_closing: "%>" (location: (1:30)-(1:32))
              ├── parsed: false
              └── valid: false
       ├── close_tag: 
          └── @ HTMLCloseTagNode (location: (1:32)-(1:37))
              ├── tag_opening: "</" (location: (1:32)-(1:34))
              ├── tag_name: "h1" (location: (1:34)-(1:36))
              ├── children: []
              └── tag_closing: ">" (location: (1:36)-(1:37))
          
       ├── is_void: false
       └── source: "HTML"
       
    └── @ HTMLTextNode (location: (1:37)-(2:0))
        └── content: "\n"

Resolves #647

@marcoroth marcoroth changed the title Rust: Add Herb Rust FFI Bidinings Rust: Add Herb Rust FFI Bindings Nov 2, 2025
@github-actions github-actions bot added documentation Improvements or additions to documentation javascript rust labels Nov 2, 2025
@marcoroth marcoroth marked this pull request as ready for review November 3, 2025 00:26
@marcoroth marcoroth merged commit a248389 into main Nov 3, 2025
11 checks passed
@marcoroth marcoroth deleted the herb-rust branch November 3, 2025 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation rust

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Herb: Rust Bindings

2 participants