A Ruby-subset interpreter built as a CRuby C extension on top of the
ASTro framework. It reuses CRuby's VALUE
representation and parser (Prism), so numbers, strings, arrays, hashes,
regexps and friends behave like their Ruby counterparts, while the
evaluator itself is a tree-walking interpreter generated from
node.def by ASTroGen.
Looking for the language reference? See
docs/spec.md. For what is/isn't implemented, seedocs/done.mdanddocs/todo.md.
abruby is a CRuby extension; you need a Ruby with development headers (the repo is developed against Ruby 4.0.x under rbenv).
sudo apt install build-essential ruby ruby-devOn other systems, install GCC/Make and a Ruby with mkmf (Ruby 3.x or
later). Prism is bundled with CRuby itself, so no extra parser setup is
needed.
From this directory:
ruby extconf.rb && make # builds abruby.so
make test # runs the full test suite (1000+ tests)
make run # runs exe/abruby on test.ab.rb
make clean # removes build artifactsUse the exe/abruby launcher. It accepts a script path or a one-liner
with -e:
./exe/abruby fib.ab.rb # run a script
./exe/abruby -e 'p(1 + 2)' # evaluate an expression
./exe/abruby -v # print the version
# Inspect the AST / disassembly:
./exe/abruby --dump -e '1 + 2 * 3' # pretty-print the AbRuby AST
./exe/abruby -d -e '1 + 2 * 3' # same as --dump=disasmA minimal script (fib.ab.rb):
def fib(n)
if n < 2
n
else
fib(n - 1) + fib(n - 2)
end
end
p(fib(34))$ ./exe/abruby fib.ab.rb
5702887abruby covers a practical Ruby subset. Highlights:
- Literals — integers (Fixnum + Bignum), floats, strings with
"#{...}"interpolation, symbols, arrays, hashes ({a: 1}/{"k" => v}),1..10/1...10ranges,/regex/,%w(..)/%i(..), heredocs,Rational(3r),Complex(2i). - Variables — locals, instance variables (
@x), globals ($x), compound assign (+=,||=,&&=), multiple assignment (a, b = 1, 2). - Control flow —
if/elsif/unless,while/until, postfix modifiers, ternary,&&/||/!,case/whenwith===,begin/rescue/ensure,raise, backtraces. - Methods & classes —
defwith default/splat args, endlessdef f = expr,attr_reader/_writer/_accessor,self.fooclass methods,super,method_missing,method(:x),respond_to?, single inheritance,includefor modules, constants (FOO = 1,A::B). - Blocks, Proc, Fiber —
{ |x| ... }/do |x| ... end,yield,block_given?, non-localreturn,next/breakwith values,Proc.new/lambda/->,Fiber.new { ... }+resume/yield/transfer. - Built-in classes —
Object,Integer,Float,String,Symbol,Array,Hash,Range,Regexp,Rational,Complex,Proc,Fiber,TrueClass/FalseClass/NilClass, plus theKernelmodule (p,raise,Rational,Complex, ...).
Example covering a few of these (test.ab.rb):
class Point
def initialize(x, y); @x = x; @y = y; end
def x = @x
def y = @y
def dist = @x * @x + @y * @y
end
sum = 0
i = 0
while i < 500_000
pt = Point.new(i, i + 1)
sum += pt.dist
i += 1
end
p(sum)By default abruby runs as a tree-walking interpreter. The same
exe/abruby front-end also drives ASTro's code store — a cache of
specialized C dispatchers compiled per AST — for AOT and profile-guided
compilation. Pick one:
./exe/abruby --plain file.ab.rb # interpreter only, no code store
./exe/abruby file.ab.rb # reuse previously-baked code if any (default)
./exe/abruby -c file.ab.rb # AOT-compile every entry before running
./exe/abruby --pg-compile file.ab.rb # run, then bake hot entries for next time
./exe/abruby --aot-compile a.rb b.rb --run main.rb # batch AOT, then runUseful flags:
| Flag | Purpose |
|---|---|
--verbose |
trace code-store operations |
--pg-threshold=N |
only PG-bake entries dispatched ≥ N times (default 100) |
--compiled-only |
abort if the default (interpreter) dispatcher is used |
--aot-only |
ignore PGC index, use AOT SD_<Horg> only |
--code-store=DIR / ABRUBY_CODE_STORE=DIR |
pick a custom store directory |
--clear-code-store (--ccs) |
wipe the store before starting |
Baked C files land under code_store/ (SD_<hash>.c for AOT,
PGSD_<hash>.c for profile-guided) and are linked into a single
all.so that cs_load swaps each node's dispatcher to.
exe/iabrb is an interactive REPL with the same code-store knobs:
./exe/iabrb # plain interpreter REPL
./exe/iabrb -c # AOT-compile each input before executing it
./exe/iabrb --pg-compile # bake hot entries after each input
./exe/iabrb -d # disasm what was compiledmake test # full suite (default build)
make debug-test # rebuild with ABRUBY_DEBUG=1, run the suite, restoreABRUBY_DEBUG=1 enables strict runtime checks (ab_verify) that catch
class/type-tag mismatches early when hacking on the evaluator.
node.def— ASTro node definitions andEVAL_*bodies; the "source of truth" for the interpreter.abruby.c— CRuby extension entry point,T_DATAtype, and the ALLOC wrappers exposed to the parser.lib/abruby.rb— Prism AST → AbRuby AST translator plusAbRuby.eval/AbRuby.dump.builtin/— per-class method implementations (integer.c,string.c,array.c, ...).docs/runtime.md— runtime data-structure tour (classes, frames, inline caches, VALUE stack, GC).benchmark/— bm_fib, bm_ack, bm_nbody, etc.; the baseline/comparison scripts live underbenchmark/report/.