Roadmap (phases)¶
go-embedded-ruby is grown test-first, one vertical capability at a time. Each phase ends end-to-end runnable and differential-tested against MRI, rather than building whole subsystems in isolation. There are nine phases, 0 through 8.
| Phase | Name | Goal | Status |
|---|---|---|---|
| 0 | Vertical slice | Integers, floats, strings, locals, arithmetic, control flow, def/recursion, puts/p — a thin slice running end to end on the VM. |
Done |
| 1 | Object model & dispatch | RObject/RClass, per-class method tables, ancestor-chain lookup, method_missing, classes + inheritance, @ivars, new/initialize, constants, modules + include, super, blocks & yield (closures, block_given?, Integer#times), and — landed since — Proc/lambda and do…end. |
Done |
| 2 | Core types & mixins | Symbols (incl. variable-name :name), Array, ordered Hash, Range (incl. beginless/endless), and Comparable/Enumerable written once in an embedded-Ruby prelude over <=>/each (Comparable into Integer/Float/String, Enumerable into Array/Range/Hash). The full argument system — required/optional/*splat/keyword (a:, b: 2)/**rest/&block, plus splat call-args. Blocks/Procs/lambdas: &block capture, proc/lambda, the stabby lambda ->(){}, &proc block-pass and Symbol#to_proc (&:sym). Object model: class methods def self.foo, attr_accessor/reader/writer, Struct.new, setter defs def name=, constant assignment and attribute assignment, endless methods def foo = expr. Plus &&/\|\|, ternary, case/when, **, string interpolation, %/format/sprintf, a broad String method set, Integer/Float numeric methods, block auto-splat, {name: value} label hashes, break/next, compound assignment, and top-level ivars. |
In progress |
| 3 | Control flow, exceptions & Fiber | Real exception objects and status-return propagation; Fiber-on-goroutine. Exception hierarchy, is_a?/kind_of?/instance_of?, raise, begin/rescue/ensure/else/retry (block- and method-level), non-local break/next, Kernel#loop, and the eager Enumerator done — every blockless iterator returns one, with next/peek/rewind/with_index/to_a and full Enumerable chaining, plus Enumerator::Lazy — lazy over arrays, ranges (incl. endless / Float::INFINITY-bounded) and enumerators, with deferred map/select/reject/filter_map/take_while/drop_while/take/drop and first/to_a/force terminals, and Fiber — cooperative coroutines on goroutines (Fiber.new/resume/Fiber.yield/alive?, values both ways, FiberError). |
Done |
| 4 | Metaprogramming | define_method, send, singleton classes, reflection, monkey-patching surfaced as API. Done so far: define_method, instance_eval/instance_exec, class_eval/module_eval/class_exec, send/public_send, respond_to?, instance_variable_get/set/defined?, itself/tap/then/yield_self, the Integer/Float/String/Array conversion methods, the hooks included/inherited/method_added/extended, define_singleton_method/extend and def obj.foo / def Const.foo singleton-method syntax, @@class variables, $global variables, string eval, Binding (binding, Binding#eval, eval(str, binding), local_variable_get/set/defined?, local_variables, receiver — eval against a captured frame's locals), and prepend — with an ancestor-chain super that walks included/prepended modules and the singleton chain (Module#ancestors/include?, Kernel modelled). |
Done |
| 5 | Full front-end | Complete the lexer/parser/compiler to full Ruby 4.0 syntax (or settle the Prism-on-wazero decision). Landed: string interpolation, keyword args, endless methods (def foo = expr), beginless/endless ranges, the stabby lambda, multiple assignment / destructuring, full pattern matching case/in (value/bind/class/array/hash/find/pin/alternative patterns, guards, one-line =>/in, deconstruct/deconstruct_keys), safe navigation &., numbered params (_1/_2) and it, heredocs (<</<<-/<<~, interpolating and literal), %w/%i arrays, and the %q/%Q/%W/%I literals. |
In progress |
| 6 | Standard library | Pure-Go stdlib coverage, with the regexp engine from the go-ruby-regexp sibling org. Regexp bridge landed: /re/imx literals + Regexp/MatchData (source/match/match?/=~/===, [] by index or name, pre_match/post_match/captures/named_captures/begin/end/size), String#=~/match/match?/scan, and sub/gsub/split over a Regexp (backref templates, blocks, captured-group interpolation, field limit). CGO=0 via go-ruby-regexp; differential-tested against MRI. Also landed: Time/Date/BigDecimal, Set/Bag, and the Go-native leaves JSON (generate/parse/pretty_generate + to_json), Digest (MD5/SHA1/SHA256/SHA512), Base64, File (path helpers + read/write/stat/delete, Errno::ENOENT), a MRI-faithful Random (seeded MT19937, bit-exact), Zlib (crc32/adler32 + Deflate/Inflate), a byte-exact Marshal (dump/load, on the pure-Go go-ruby-marshal engine), Thread/Mutex/Queue on an emulated GVL (one Ruby thread at a time, MRI memory model, race-free), IO/StringIO ($stdout/$stderr/$stdin as real objects; puts/print/p route through $stdout so reassigning it to a StringIO captures output) and Dir (entries/children/glob, mkdir/rmdir/chdir, Errno::ENOENT/Errno::EEXIST), and File.open streams (File < IO, modes r/w/a/+, block form auto-flush/close, File.readlines/foreach). |
In progress |
| 7 | Build toolchain | rbgo build. Landed: ahead-of-time method compilation to native Go (bytecode → Go → machine code), with unboxed int64 integer kernels that deopt to the interpreter on overflow/÷0 — the generated fib(30) beats MRI + YJIT ~4× while staying correct for every input (AOT compiler). Closed-world builds landed (rbgo build --closed): the program is baked in as bytecode (and the prelude loads from frozen bytecode), the lexer/parser/compiler are dropped behind the rbgo_closed build tag, and a require-graph scan reports any eval/require that would then raise — yielding a smaller, self-contained binary that runs with no source file. (Per-class stdlib tree-shaking beyond the front-end was measured and skipped — the Go-implemented bindings are small pure-Go libraries, under 2% of a runtime-dominated binary; the front-end drop is the real saving.) |
Done |
| 8 | Conformance & performance | ruby/spec conformance and representation/perf work (tagging, fast paths, allocation). Started: allocation cuts — small-integer interning (object.IntValue reuses a shared box for ints in [-256, 1024]; a small-int loop drops from ~245k allocs to 1) and frame-environment recycling (exec pools per-call *Envs via a capture-tracked free-list — fib's call allocations halve, ~14% faster). Conformance & benchmark ladder: a three-way differential oracle (rbgo / MRI / JRuby, scripts/oracle.sh), then pure-Ruby gem test suites as corpora (ActiveSupport core_ext), then real-world workloads. A heavyweight front-end sweep now accepts ~100% of all valid Ruby across Rails + Puppet (parse + compile Rails 99.82% / Puppet 100%, 0 over-permissive), and on the runtime side the real puppet apply CLI runs end-to-end (Puppet 8.11.0): require "puppet" boots the framework and the genuine Puppet::Util::CommandLine → Puppet::Application::Apply path (real OptionParser, all types + providers, settings catalog applied to disk, then the user catalog via the transaction / RAL) applies a notify resource and emits real Puppet output (Notice: hi from rbgo cli), exiting 0. Active next milestone: real file/package/service resource providers (host convergence) + run-report persistence. |
In progress |
See Phase 0 — Vertical slice for what runs today.