A pure-Go (CGO=0) Ruby front-end — a lexer, a recursive-descent + Pratt parser, and an AST for Ruby source. No cgo, no Prism, no shelling out to Ruby: the single thing the Go ecosystem lacked for building Ruby tooling (linters, formatters, analysers, doc generators, LSP servers, transpilers) in pure Go.
It was extracted from the go-embedded-ruby interpreter — which now consumes it — and is developed test-first against MRI Ruby 4.0.5.
go get github.com/go-ruby-parser/parserimport "github.com/go-ruby-parser/parser"
prog, err := parser.Parse(`
def fib(n)
n < 2 ? n : fib(n - 1) + fib(n - 2)
end
puts fib(20)
`)
if err != nil {
// err is a parse error with a line number
}
// prog is an *ast.Program; walk prog.Body ([]ast.Node).The AST node types live in github.com/go-ruby-parser/parser/ast; the
token kinds in .../token; the stateful lexer in .../lexer.
A broad, practical subset of Ruby 4.0, all differential-tested against MRI:
- Literals: integers (with
Bignum/arbitrary precision, radix0x/0o/0b/0d, underscores), floats (incl. scientific1.5e3), strings (double- and single-quoted, interpolation, heredocs<</<<-/<<~,%q/%Qliterals, the\a/\b/\v/\f/\s/\n/\t/\r/\e/\0escapes), symbols (incl. quoted/operator),%w/%i/%W/%Iarrays, arrays, hashes (incl. the{x:}value-shorthand), ranges (incl. beginless/endless), regexps (/re/imx),true/false/nil. - Operators: arithmetic, comparison/
<=>,==/===, bitwise/shift,&&/||/and/or/not, ternary,::scope, safe navigation&., compound assignment (+=,-=,*=,/=,%=,<<=,||=,&&=). - Control flow:
if/unless/while/until(block and modifier),case/when,case/inpattern matching (array/find/hash/pin/ alternative/range patterns, guards, one-line=>/in),begin/rescue/else/ensure/retry,break/next/return,loop. - Methods/blocks: required/optional/
*splat/keyword/**rest/&blockparams, endless methods (def f = expr), setters, operator/[]/[]=method names, operator-method calls (1.+(2)),{ }/do…endblocks,(a, b)destructuring group params, stabby lambdas->(){}, numbered params (_1) andit,yield,super, multiple-valuereturn a, b. - Classes/modules/metaprogramming:
class/module, inheritance,@ivars,@@class variables, constants, singleton method defs (def self.foo/def obj.foo/def Const.foo), global-variable assignment ($g = …), multiple assignment / destructuring, adjacent string-literal concatenation ("a" "b").
On the both-accept corpus (the 615 harvested snippets + 110 real CRuby-4.0.5
stdlib files both engines parse), go-ruby-parser beats MRI's reference C
parser: it parses 1.5× faster than RubyVM::AbstractSyntaxTree.parse on
small snippets and 2.1× faster on real stdlib files, ~6× faster than
Ripper.sexp, and the lexer is ~24–30× faster than Ripper.lex — all while
building a full Go AST. Methodology, full parity tables, and the allocation
hotspots / action items are in BENCHMARKS.md; reproduce with
benchmarks/run.sh (an isolated module, outside the coverage
gate).
The following are not yet parsed (they remain on go-embedded-ruby's roadmap; contributions welcome):
- paren-less command calls with keyword/splat/block args (
foo a: 1) - default block parameters (
{ |a = 1| }) — splat block params ({ |*a| }) are supported - the positional
Class(a)find-pattern (Class[a]is supported)
BSD-3-Clause © the go-ruby-parser/parser authors.
Being pure Go (CGO=0), this library also compiles to WebAssembly — both
GOOS=js GOARCH=wasm (browser / Node.js) and GOOS=wasip1 GOARCH=wasm (WASI).
CI builds both targets on every push, alongside the six 64-bit native/qemu arches.
GOOS=js GOARCH=wasm go build ./... # browser / Node
GOOS=wasip1 GOARCH=wasm go build ./... # WASI (wasmtime, wasmer, wasmedge, …)