A pure-Go implementation of Ruby — one static binary, full dynamism, zero cgo.
This repository is the interpreter: a compiler that lowers Ruby to bytecode, and
a stack VM (mruby/YARV lineage) that runs it. The Ruby front-end (lexer,
parser, AST) is the standalone pure-Go
go-ruby-parser module, which this
interpreter imports. The front-end is embedded in the binary, so eval and
runtime require keep working. Ruby
objects are Go heap objects, so Go's garbage collector is reused. Dispatch
goes through mutable per-class method tables, which is what makes
monkey-patching, define_method and method_missing free.
🌐 Website · 📚 Documentation · 🧭 Roadmap
rbgo is increasingly assembled from a family of standalone pure-Go (CGO=0), MRI-compatible libraries — each its own org with 100% coverage and 6-arch CI — that the interpreter composes and binds as native modules. The design principle is a clean seam: a stdlib piece whose work is pure compute and needs no interpreter — a regexp matcher, an ERB compiler, a YAML emitter, a Marshal codec, an OptionParser argv engine — becomes a reusable standalone library, while only the thin interpreter-dependent glue (the Ruby-object binding) stays in rbgo. The same lever that produced the front-end (go-ruby-parser) is now applied across the stdlib. The benefit cuts both ways: rbgo still ships as a single CGO=0 static binary, and every extracted piece is independently reusable, tested and 6-arch by any Go program — no interpreter required.
The go-ruby-* family is 104 standalone pure-Go modules — all CI-green, 100%
coverage, 6-arch — each its own org. The go.mod currently binds 71 into
rbgo as native modules (binding the rest in progress):
| Library | Role | Org · landing |
|---|---|---|
| go-ruby-parser | Ruby lexer / parser / AST front-end (embedded, so eval/require keep working) |
org · site |
| go-ruby-regexp | Onigmo-compatible regexp engine (incl. \b/\B, char-class literals) |
org · site |
| go-ruby-erb | ERB template compiler | org · site |
| go-ruby-marshal | Marshal (dump/load), byte-exact with MRI |
org · site |
| go-ruby-yaml | Psych-compatible YAML emitter + loader | org · site |
| go-ruby-format | sprintf / % / format engine |
org · site |
| go-ruby-optparse | OptionParser argv engine |
org · site |
| go-ruby-strscan | StringScanner (strscan) |
org · site |
The full bound family (alphabetical, all native modules in go.mod):
abbrev, addressable, base64, bcrypt, benchmark, bigdecimal,
builder, cgi, chronic, cmath, commonmark, csv, date,
did-you-mean, digest, dotenv, dry-struct, dry-types, dry-validation,
erb, faker, find, format, getoptlong, haml, hcl2, ipaddr,
jbuilder, json, jwt, kramdown, liquid, logger, mail, marshal,
matrix, mime-types, money, msgpack, mustache, nokogiri, oauth2,
observer, optparse, ostruct, parser, pathname, prettyprint, prime,
pstore, public-suffix, regexp, resolv, rexml, rouge, rqrcode,
rspec, scanf, securerandom, set, shellwords, slim, sqlite3,
strscan, toml, tsort, tzinfo, unicode-normalize, uri, yaml,
zlib — each at github.com/go-ruby-<name>/<name>.
Beyond the go-ruby-* family, the scientific / container stack binds the
pure-Go go-ndarray,
go-fft, go-images
and go-composites libraries the same way (see
Supported today below).
A major conformance + performance campaign measured rbgo's pure-Go front-end (parse + compile) against the MRI 4.0.5 oracle on the two largest real-world Ruby codebases and a suite of popular libraries. All figures are front-end acceptance, not whole-application execution (see the honesty note below).
- Parse: 99.96 % of Rails + Puppet (5577 / 5579
.rbfiles). The only 2 misses are intentional syntax-error fixtures MRI itself rejects — i.e. 100 % of all valid Ruby in the corpus parses. - End-to-end (parse + compile): Rails 99.82 % (3417 / 3423), Puppet 100.00 % (2154 / 2154).
- 0 over-permissive — rbgo never accepts Ruby that MRI rejects.
- Library parse-conformance: RuboCop 99.7 %, Sinatra / Jekyll / Thor / Kramdown / dry-struct 100 %, Homebrew 98.7 %, Chef 99.1 %, concurrent-ruby 98.3 %, Asciidoctor 93.8 %.
- RSpec DSL usage: 10/10 byte-identical to MRI.
- The journey (Rails end-to-end, across 5 parser rounds + 5 rbgo activation rounds): 20.7 % → 46.3 % → 68.7 % → 81.2 % → 86.7 % → 93.8 % → 98.4 % → 99.82 %.
Features added in the campaign include :: constant-paths (qualified class /
module names, superclass, leading ::), paren-less command calls with
args/kwargs, class << self (singleton class), masgn to any target
(ivar/cvar/gvar/attr/index/constant + nested destructuring), alias/undef,
anonymous params + forwarding (def f(*, **, &) / g(...)), special globals
($$ etc.), shorthand hash {x:}, quoted / operator / char-literal symbols,
rationals & imaginaries (2r / 3i), unicode identifiers, for…in…end loops,
block keyword params, begin-less rescue/ensure, and !~ — on top of the
pre-existing core (full object model, metaprogramming, Fiber/Thread, Marshal,
regexp, the scientific bindings, and the js/wasm target) detailed below.
Performance: a 6-runtime comparative suite (BENCHMARKS.md) pits rbgo and rbgo+AOT against MRI, MRI+YJIT, JRuby and TruffleRuby. The rbgo interpreter runs ~3–6× MRI on compute and at parity on I/O-bound work; rbgo+AOT beats MRI+YJIT 18–24× on loop/fib (the only runtime here that beats YJIT); TruffleRuby is the compute ceiling.
Honesty note. "99.82 % of Rails" means rbgo's front-end parses and compiles that fraction of Rails's
.rbfiles — not that rbgo runs Rails. Running a full application additionally needs the runtime stdlib surface and C-extension equivalents. That surface is now real enough that the realpuppet applyCLI runs end-to-end under rbgo (see Running Puppet below), convergingnotify,fileandexecresources; the broader resource providers (package/servicehost convergence) remain in progress. What is established is that the Ruby language / front-end is essentially complete on real-world code, and that the runtime is far enough along to boot a real application; whether any given application runs end-to-end remains application-specific work. Details: CONFORMANCE-RAILS-PUPPET.md, CONFORMANCE-LIBRARIES.md, CONFORMANCE-RSPEC.md.
Beyond parsing real-world Ruby, rbgo now runs the real Puppet
puppet apply CLI end-to-end. require "puppet" fully boots the framework
(Puppet 8.11.0) on a pure-Go CGO=0 rbgo — its pure-Ruby gem dependencies
(semantic_puppet, concurrent-ruby, deep_merge, fast_gettext, facter,
racc, …) load on the $LOAD_PATH — and a manifest then travels the complete
Puppet path: the real Puppet::Util::CommandLine → Puppet::Application::Apply
entry point (a real OptionParser), every Puppet type + provider loaded, the
settings catalog applied (creating Puppet's config dirs on disk), and finally the
user catalog applied through the transaction / RAL. Driving the genuine CLI emits
real Puppet output and exits 0:
$ rbgo run puppet_apply.rb # ARGV = apply -e 'notify { "hello": message => "hi from rbgo cli" }'
Notice: Compiled catalog for in environment production in 0.00 seconds
Notice: hi from rbgo cli
Notice: /Stage[main]/Main/Notify[hello]/message: defined 'message' as 'hi from rbgo cli'
That is the actual notify resource type applying through the transaction and the
Resource Abstraction Layer — not a notice(...) evaluator print. Reaching the CLI
added a real OptionParser (optparse), File::Stat / FileTest + on-disk
filesystem operations, and a batch of deep Ruby fixes (class_eval lexical
scope, return inside define_method, class-method super, String#chomp(sep),
…).
Three resource types now converge end-to-end through the transaction / RAL:
notify, file (the catalog creates / manages a real file on disk), and
exec — which runs its command through pure-Go process execution, honouring
the onlyif / unless / creates / path guards (so an already-converged
exec is correctly skipped). puppet apply exits cleanly, the YAML run
report / state round-tripping through the pure-Go Psych emitter / loader.
Reaching this exercised a large slice of the runtime, each gap reduced to a minimal rbgo-vs-MRI repro before fixing:
- Language / VM conformance:
autoload, frame-basedException#backtrace/set_backtrace/full_message, interpolated regexp literals, non-local blockreturn,Symbol#intern,NilClassconversions,Arrayslice-assignment,Module.new(real anonymous module running its block as a body),extendtransitivity (a module's transitively-included methods become class methods), setter expressions returning their RHS, nested constant namespaces, and method-visibility enforcement — among dozens more. - Pure-Go stdlib modules added on the path to boot and run the CLI:
ERB(template engine),openssl(real crypto, not a stub),net/http,resolv,tmpdir,Process,StringScanner(strscan),Find,getoptlong,syslog,fileutils, a realOptionParser(optparse),File::Stat/FileTest+ on-disk filesystem operations,objspace, and more — eachrequire-able and CGO=0.
This is the C-extension → pure-Go shim strategy in action: a real Ruby application ships as a single static CGO=0 binary because the C-backed gem APIs are backed by pure Go. Puppet validates the approach end to end — its dependency tree is pure Ruby, so it loads as-is.
Frontier (honest): what runs end-to-end is the
puppet applyCLI convergingnotify,fileandexecresources through the full pipeline (boot → parse → compile → transaction / RAL → output → clean exit). The next frontier is the broader resource providers —user/groupare provider-ready, whilepackage/serviceneed a host package manager / systemd and root. So the language, front-end, boot, CLI/apply machinery and the first convergent providers are real today; the remaining system-state providers are the work in progress.
Supported today (every feature differential-tested against MRI Ruby 4.0.5 and JRuby):
-
Values: integers (
int64, with automatic Bignum promotion on int64 overflow and arbitrary-precision integer literals, radix literals0x/0o/ bare-0-octal/0b/0dwith underscores), floats, strings, symbols (incl. operator-method symbols:+/:<</:[]=/:<=>, usable withreduce(:+)/inject/send(:+, x)), arrays, hashes, ranges (incl. beginless/endless),ComplexandRationalnumbers,true/false/nil,self,Proc/lambda,Regexp/MatchData,Struct. -
Operators: arithmetic (
+ - * / %, Ruby floor division,**), comparison/<=>,==/===, bitwise/shift (<< >> & | ^ ~, arbitrary precision),&&/||, ternary, ranges,::constant scope (Math::PI,Foo::BAR); correct negative-literal precedence (-2.abs == 2,-2**2 == -4). -
Control flow:
if/elsif/else,unless,while/until,case/when, statement modifiers (incl. modifierrescue,expr rescue fallback),begin/rescue/else/ensure/retry,break/next,Kernel#loop, andFiber(cooperative coroutines —Fiber.new/resume/Fiber.yield/alive?). -
Concurrency:
Thread(new/start/join/value/status/current/main/list/pass, thread-locals via[]/[]=, exception propagation on join),Mutex(lock/unlock/try_lock/synchronize/owned?) andQueue(blockingpush/pop,close), on an emulated GVL — one Ruby thread runs at a time, matching MRI's memory model (race-free under the Go race detector). -
Pattern matching (
case/in): value, variable-binding, class/constant, array (incl. splat and nested), hash (deconstruct_keys,**rest/**nil), find ([*pre, x, *post]), pin (^x) and alternative (a | b) patterns; the=> namebinding suffix, guards (if/unless), the one-line formsexpr => patternandexpr in pattern, andNoMatchingPatternError— via thedeconstruct/deconstruct_keysprotocols. -
Assignment: multiple assignment / destructuring (
a, b = 1, 2, swap,x, *rest = …,*init, last = …), compound assignment (+= -= *= /= %= <<= ||= &&=), global variables ($g, plain and compound). -
Methods: required / optional /
*splat/ keyword (a:,b: 2) /**rest/&blockparameters, setter defs (def name=), endless methods (def foo = expr), singleton method defs on any object (def obj.foo/def Const.foo), recursion,return,super. -
Blocks / Procs / lambdas:
{ }/do…endclosures,yield,block_given?,&blockcapture, block params with destructuring (|(a, b)|) and rest (|*rest|,|head, *rest|), numbered params (_1/_2) andit,Proc/lambda/stabby->(){},&procblock-pass andSymbol#to_proc(the&:symshorthand). -
Classes & modules: inheritance,
@ivars,@@class variables(shared down the superclass hierarchy),new/initialize, constants and constant assignment, class methods (def self.foo), modules +include/prepend(mixins, with full ancestor-chainsuperthrough included/prepended modules and the singleton chain),Module#ancestors/include?,attr_accessor/reader/writer,Struct.new. -
Metaprogramming: dynamic dispatch via mutable method tables,
method_missing,send/public_send,respond_to?,define_method,instance_eval/instance_exec,class_eval/module_eval/class_exec,instance_variable_get/set/defined?, stringeval(the embedded front-end compiling Ruby at runtime),Binding(binding,Binding#eval,eval(str, binding),local_variable_get/set/defined?,local_variables,receiver— capturing a frame's locals so eval'd code reads and writes them), and the class/module hooksinherited/included/prepended/method_added/extended. -
Runtime loading:
require/require_relativeload, compile and run a.rbfile once (relative + search-path resolution,LoadErroron miss,true/falsereturn) — the embedded front-end loading code at runtime. -
Strings: mutable (reference semantics) with
<</concat/replace/prepend/ insert/[]=/slice!/the bang methods andfreeze/FrozenError; interpolation, heredocs (<</<<-/<<~),%w/%iand%q/%Q/%W/%Iliterals, the\a/\b/\v/\f/\s/\n/\t/\r/\e/\0escapes,%/format/sprintf, case/strip/split/each_char/lines/succ(next) and friends. -
Regular expressions:
/re/imxliterals,Regexp/MatchData,=~/match/match?/scan/gsub/sub/split, and the match globals$~/$1..$N/$&/$`/$'— running on the standalone pure-Go go-ruby-regexp engine, so the build stays CGO=0. -
Standard library leaves:
JSON(generate/dump/pretty_generate/parse+Object#to_json, with object key order preserved and MRI-matching number/escape formatting),Digest(MD5/SHA1/SHA256/SHA512—hexdigest/digest/base64digest),Base64, andZlib(crc32/adler32+Deflate/Inflate) — eachrequire-able and pure-Go. -
Marshal(dump/load/restore+MAJOR_VERSION/MINOR_VERSION) — Ruby's binary serialization, byte-for-byte identical to MRI across Integer/Bignum, Float, Symbol, String, Array, Hash (incl. defaults), and the symbol/object-link tables (shared objects and cycles round-trip). Runs on the standalone pure-Go go-ruby-marshal engine, so the build stays CGO=0. -
File & Random:
File— path helpers (basename/dirname/extname/join/split/expand_path) and filesystem ops (read/write/exist?/file?/directory?/size/delete), raisingErrno::ENOENTfor missing paths;Random— a bit-exact reimplementation of MRI's seeded MT19937, soRandom.new(seed)/srand+randreproduce MRI's sequence. -
IO:
IOwith$stdout/$stderr/STDOUT/STDERR/$stdinas real objects (write/<</print/puts/printf/putc/sync/flush/close),StringIO(require "stringio") — an in-memory IO with the full read side (read/gets/getc/readline/readlines/each_line/each_char,pos/seek/rewind/truncate/eof?/string), andKernel#warn.Kernel#puts/print/pwrite through the current$stdout, so reassigning it to aStringIOcaptures output, as in MRI.File.open(modesr/w/a/r+, block-scoped auto-close) returns a file-backed IO (File<IO) with the same read/write protocol, plusFile.readlines/File.foreach. -
Dir:entries/children/glob/[],exist?/empty?,pwd/home,mkdir/rmdir/chdir(block-scoped),each_child/foreach, raisingErrno::ENOENT/Errno::EEXISTas MRI does. -
Collections: Array / Hash / Range with
Enumerable(map/select/reduce/minmax/…) andComparable, both written once in embedded Ruby; Array bang methods (map!/sort!/select!/reject!/compact!/uniq!/reverse!), structural/combinatorial ops (transpose/product/combination/to_h), theHash[…]constructor, String ranges (("a".."e")iterating viaString#succ), andRange#step/Integer#step(integer and float walks, both directions). -
Enumerator: every blockless iterator (
each/map/select/reject/each_slice/each_cons/each_with_index/times/upto/each_char/…) returns anEnumerator(MRI semantics) withnext/peek/rewind/size/to_a,with_index/each_with_index,Kernel#enum_for/to_enum, and fullEnumerablechaining ([1,2,3].map.with_index { |x, i| … }); plusEnumerator::Lazy(lazy) — deferredmap/select/reject/filter_map/take/take_while/drop/drop_whileover finite or infinite ((1..Float::INFINITY).lazy) sources, materialised byfirst/to_a/force. -
Numeric tower:
Integer/Float/Rational/Complexunder a sharedNumeric(carryingComparable);Module#ancestors/include?,Class#superclass. -
Objects:
dup/clone/freeze/frozen?,equal?,object_id/__id__(MRI's deterministic immediate-value ids, stable per reference object),instance_variable_get/set. -
Math: the
Mathmodule (sqrt/cbrt/exp/log/log2/log10, the trig and hyperbolic functions,atan2/hypot/pow) withMath::PI/Math::E. -
NDArray: a NumPy-style n-dimensional array —
zeros/ones/full/arange/from, element-wise+ - * /with scalar broadcasting, ufuncs (sqrt/exp/log/sin/cos/abs), reductions (sum/mean/max/min/prod/argmax/argmin),matmul/dot,transpose/reshape/flatten,shape/to_a/[]— binding the pure-Go go-ndarray library, no cgo / no NumPy. -
Image: a scikit-image-style image processor —
Image.new/load/save, pixelget/set, filters (gaussian_blur/box_blur/median/sharpen), edges (sobel/prewitt/scharr/laplacian/canny), morphology (erode/dilate), geometry (resize/rotate90/crop/flip_*), colour (grayscale/invert/rgb_to_hsv/otsu) — binding the pure-Go go-images library, no cgo. -
FFT: an
FFTmodule — 1-D (fft/ifft/rfft/irfft), N-D and 2-D (fftn/ifftn/fft2/ifft2), bin-frequency helpers (fftfreq/rfftfreq), window functions (hann/hamming/blackman/blackman_harris/bartlett), and spectral helpers (psd/spectrogram) — binding the pure-Go go-fft library, anumpy.fft-style transform with no cgo / no FFTW, returningComplexspectra. -
Set: Ruby's
Set—new/[],add(<<)/add?/delete/merge/clear,include?/member?/size/length/count/empty?,subset?/superset?,union(|)/intersection(&)/difference(-),each/to_a/to_set— binding the pure-Go go-composites/set library. -
Time: Ruby's
Time—now/at/parse, arithmetic (+/-/<=>),strftime/strptime,year/month/mday/hour/min/sec/wday, weekday predicates (monday?…sunday?),utc/getutc/zone,to_i/to_f— binding go-composites/time. -
Date: Ruby's
Date—new/parse,+/-/<</>>andnext_day/prev_day/next_month/prev_month,year/month/mday/wday/yday/cwday,leap?, comparisons — binding go-composites/date. -
BigDecimal: arbitrary-precision decimal —
+ - * / **,sqrt/abs/ceil/floor/round/pow,to_f/to_i,zero?, comparisons — binding go-composites/bigfloat. -
Bag: a multiset / counter (element → multiplicity) —
add(<<)/delete,count/size/distinct/most_common,union/difference/intersection,include?/each/to_a— binding go-composites/bag. -
AOT compiler (
rbgo build): lowers a program's methods to native Go and links a specialised binary. Pure integer methods become unboxedint64kernels with an overflow/÷0deopt back to the interpreter — the generatedfib(30)runs ~4× faster than MRI+YJIT while staying correct for every input. See docs/aot-compiler.md. -
Closed-world binary (
rbgo build --closed): bakes the whole program in as bytecode (and loads the prelude from frozen bytecode), then drops the lexer/parser/compiler from the link. The result runs with no source file and no front-end — a smaller, self-contained binary.rbgo buildreports whicheval/requirecalls (if any) would raise in the closed binary, since there is no front-end left to compile source at runtime. -
WebAssembly (
GOOS=js GOARCH=wasm) is a supported target. rbgo runs in the browser two ways: the playground ships the full interpreter (front-end + VM + numeric/image stack) as a wasm module that evaluates arbitrary Ruby in the page (see Run in the browser); andrbgo build --closed --target wasm app.rbcross-compiles a closed-world wasm module that runs that one program (no front-end linked) and can drive the page's DOM/Canvas through the built-inJSmodule (JS.document/JS.log/JS::Ref#call/JS.raf).
100% coverage is enforced in CI across all six 64-bit targets (amd64, arm64,
riscv64, loong64, ppc64le, s390x) and three OSes. Phase 8 (conformance and
representation/perf tuning) is well advanced: the conformance campaign above
brought the front-end to ~100 % parse / 99.82 % parse+compile on real-world Ruby,
and on the performance side small-integer interning and capture-tracked
frame-environment recycling have cut call-path allocations (a small-int loop from
~245k allocations to 1; recursion's call allocations halved, ~14% faster), with
the 6-runtime benchmark suite (BENCHMARKS.md) tracking rbgo vs
MRI / YJIT / JRuby / TruffleRuby. The road from "parses + compiles" to "runs whole
applications" — the runtime stdlib + C-extension surface — is now well underway:
the real puppet apply CLI runs end-to-end (see Running Puppet above),
converging notify, file and exec resources and exiting cleanly, with the
broader resource providers (package / service) the active next milestone.
See the
roadmap.
Requires Go 1.26.4+.
# run a one-liner
go run ./cmd/rbgo run -e 'puts 1 + 2' # => 3
# run a file
cat > fib.rb <<'RB'
def fib(n)
if n < 2
n
else
fib(n - 1) + fib(n - 2)
end
end
puts fib(20)
RB
go run ./cmd/rbgo run fib.rb # => 6765
# build the CLI
go build -o rbgo ./cmd/rbgo
./rbgo fib.rb
# AOT-compile a program's methods to native code and link a specialised binary
./rbgo build -o fib fib.rb # fib (the method) becomes native int64
./fib fib.rb # => 6765
# closed-world: bake the program in as bytecode and drop the front-end
./rbgo build --closed -o fib fib.rb # no lexer/parser/compiler linked
./fib # => 6765 (runs with no source file)
# WebAssembly: cross-compile a closed-world program to a browser wasm module
./rbgo build --closed --target wasm -o app.wasm app.rb # GOOS=js GOARCH=wasmGOOS=js GOARCH=wasm is a first-class target. Everything is pure Go with cgo
disabled, so the interpreter, the numeric stack and the cgo-free image pipeline
compile to a single wasm module and run entirely in the browser — there is no
server-side code. There are two ways to ship Ruby to the browser:
1. The playground — full interpreter in wasm. A self-contained page (Ruby
REPL + a load→gaussian_blur/sobel/canny→render image demo) lives in
web/. It builds cmd/wasm, which links the whole front-end (lexer,
parser, compiler) and VM, so the page can evaluate arbitrary Ruby typed by the
user:
./web/build.sh serve # build web/rbgo.wasm and serve http://localhost:8080The module publishes rbgoEval(src) and rbgoImage(src, bytes) on the JS global
object; see web/README.md for the bridge.
2. rbgo build --target wasm — a closed-world wasm app. To ship one Ruby
program (not a REPL), AOT-bake it into a closed-world wasm module that drops the
front-end:
./rbgo build --closed --target wasm -o app.wasm app.rb--target wasm requires --closed (the wasm entry runs the embedded program,
then parks the Go runtime with select{} so JS callbacks keep firing). The
program can reach the page through the built-in JS module — JS.global,
JS.window, JS.document, JS.log, JS::Ref#get/set/call/[]/on for
DOM and Canvas, and JS.raf { |t| … } for an animation loop — so a closed-world
wasm app can render and handle events with no JavaScript of its own. Serve the
emitted app.wasm next to Go's wasm_exec.js loader.
cmd/rbgo/ CLI: run, build (+ build --closed [--target wasm]; repl later)
cmd/wasm/ GOOS=js GOARCH=wasm playground front-end (see web/) + native stub
cmd/aotgen/ regenerates the AOT differential suite (go:generate)
cmd/freeze-prelude/ regenerates the frozen prelude bytecode (go:generate)
web/ browser playground: index.html, build.sh (rbgoEval/rbgoImage)
internal/
compiler/ AST → bytecode (ISeq), local-slot resolution
bytecode/ instruction set + ISeq
vm/ stack-machine interpreter, arithmetic, builtins
(front-end isolated behind the rbgo_closed build tag)
aot/ AOT compiler: bytecode → Go (level-1/3 kernels, FreezeISeq)
object/ Value interface + concrete value types
docs/ plan-rbgo.md (the roadmap), aot-compiler.md
go test ./...
go test -coverpkg=./internal/... -coverprofile=cov.out ./internal/...
go tool cover -func=cov.out | tail -1If a parent go.work is present, prefix commands with GOWORK=off.
Correctness is judged against independent reference implementations of
Ruby 4.0 — MRI (CRuby) 4.0.5 and JRuby, with TruffleRuby being added
as a third reference (conformance + performance). The differential oracle
scripts/oracle.sh runs a snippet through rbgo, MRI and
JRuby and flags any divergence:
scripts/oracle.sh -e 'p (1..10).select(&:even?).map { |x| x**2 }'Beyond synthetic tests, the bar is real-world Ruby: idioms and test suites
from reference applications — Ruby on Rails (ActiveSupport's pure-Ruby
core_ext) and OpenVox/Puppet (Ruby-heavy manifest evaluation) — drive the
remaining work by demand and double as conformance corpora and performance
baselines (pure-Go CGO=0 vs CRuby's C, JRuby's JVM JIT and TruffleRuby's Graal).
The heavyweight front-end (parse + compile) conformance results — Rails 99.82 %,
Puppet 100 %, ~100 % of all valid Ruby parsed, plus the per-library table — are
in CONFORMANCE-RAILS-PUPPET.md,
CONFORMANCE-LIBRARIES.md and
CONFORMANCE-RSPEC.md; reproduce with
scripts/conformance/heavyweight/sweep.sh.
See docs/plan-rbgo.md for the full architecture, the 9-phase plan (Phase 0 vertical slice → Phase 8 conformance & performance), the risk register, and the decision journal. The regexp engine is developed separately as a pure-Go reimplementation of Onigmo in go-ruby-regexp/regexp.
BSD-3-Clause. See LICENSE.
