// GROOVYRS — ENGINEERING REPORT

groovyrs v0.1.9 · Groovy on fusevm · lex/parse → AST → bytecode → Cranelift JIT · no bespoke VM, no JVM · MIT · in active development

Docs GitHub
// Color scheme

>_ENGINEERING REPORT

groovyrs is a compiled Groovy runtime in Rust, hosted on the fusevm bytecode VM and its three-tier Cranelift JIT — no bespoke VM and no JVM. This report describes the architecture, the value model, the current state of the tree, and the dependency posture. The statements below are facts about the design and the manifest, not aspirational metrics.

Groovy
fusevm language host
3
Cranelift JIT tiers
v0.1.9
version
MIT
license · free / OSS

Summary

Apache Groovy compiles to JVM bytecode and runs on the JVM. groovyrs takes a different path: it lexes and parses Groovy script source to an AST, lowers that to fusevm bytecode, and runs it on a compiled VM with a Cranelift JIT — with no JVM present. Arithmetic and comparison operators lower to native VM ops so the JIT can trace hot loops; Groovy-specific behaviour is served by a strict numeric hook and a small set of registered builtins. It joins fusevm alongside zshrs, stryke, awkrs, elisp, rubylang, and pythonrs, and reuses that shared bytecode VM and JIT rather than shipping its own.

The current tree implements the lexer/parser, AST-to-bytecode lowering, the Groovy semantics that fusevm's default flavour does not provide (the BigDecimal value model, String +, Groovy truthiness and value printing), user functions, classes and interfaces, closures, GStrings, list/map literals with the closure-driven GDK and the spread operator, exceptions, switch in both its statement and expression forms, power assert, the control-flow and range constructs, the standalone groovy binary, a bytecode disassembler, and the editor tooling: a Language Server (--lsp) and a Debug Adapter (--dap). Behaviour is checked against Apache Groovy by a frozen differential parity replay and a differential fuzzer. See BUGS.md for the honest ledger of what is still missing.


Hosting on fusevm

groovyrs contains no virtual machine or JIT of its own, and no JVM. The execution path is:

Groovy source  →  lexer  →  parser → AST  →  lower to fusevm bytecode  →  fusevm VM + Cranelift JIT
                                                    │
                                     strict numeric hook + print / division builtins (Groovy semantics)

Shared engine

fusevm is pulled from crates.io with the jit, jit-disk-cache, and aot features. JIT and VM improvements land once and benefit zshrs, stryke, awkrs, elisp, rubylang, pythonrs, and groovyrs together.

Native arithmetic

Operators lower to native fusevm ops; a strict numeric hook supplies Groovy semantics for the cases the VM does not compute natively: String concatenation and value comparisons for a non-numeric operand, Integer-range overflow, and an integral/double pair whose integer is past 253 — promoted to double, which is Groovy's own answer.

Persistent native code

jit-disk-cache persists compiled machine code across runs, keyed by chunk hash, so warm starts skip recompilation of hot loops.

BigDecimal division

Groovy's / lowers to a division builtin: two integers divide to a BigDecimal whether or not the division is exact (4/2 is the BigDecimal 2, 7/23.5); a decimal operand forces decimal division.


Value model

groovyrs owns a host-side object heap keyed by fusevm's opaque Value::Obj handle — closures, class instances, insertion-ordered maps, and BigDecimal decimals live there, while fusevm carries only the handle. Beyond that heap, Groovy semantics fusevm's default awk/shell flavour does not provide are supplied at four points: printing (println/print format through Groovy's toString rules — true/false, a whole decimal as 3.0, null), / division (BigDecimal promotion so 7/2 is 3.5 and 1/3 is 0.3333333333), decimal arithmetic (an unsuffixed literal is a real java.math.BigDecimal, so scale propagates: 1.25 * 0 is 0.00), and + overloading (String concatenation when either operand is a String). All four are supplied through a strict numeric hook and registered builtins, so integer and double arithmetic runs on the native fast path and the JIT — except where fusevm declines to answer a primitive pair itself (an Integer-range overflow, or an integral/double mix whose integer is past 253 and so has no exact double image), which the hook answers with the same value the native path would.

A bare name = … with no prior declaration creates a script binding, as in Groovy; an uninitialized def reads as null. Locals are addressed by name through GetVar/SetVar in a single script frame.


Component status

ComponentStateNotes
lexer / parser → ASTImplementedNewline-significant tokenizer, ../..< ranges, the Groovy script grammar (src/lexer.rs, src/parser.rs).
AST → fusevm bytecodeImplementedNative arithmetic + CallBuiltin host dispatch; no local VM; per-statement source lines (src/compiler.rs).
Groovy semantics (/, +, printing)ImplementedBigDecimal division, String concatenation, Groovy value formatting (src/host.rs).
control flow / rangesImplementedif/else, while, C-style for, for (x in a..b), break/continue.
standalone groovy binaryImplementedFiles and -e one-liners (src/main.rs).
disassembler (--disasm)Implementedfusevm bytecode listing with source line numbers.
LSP server (--lsp)ImplementedDiagnostics from the runtime parser, keyword/command completion, hover (src/lsp.rs).
DAP adapter (--dap)ImplementedPer-statement line-marker compile mode; breakpoints, stepping, stackTrace/variables over the single script frame (src/dap.rs).
parity replay vs Apache GroovyImplementedExample corpus diffed byte-for-byte against a frozen Groovy snapshot (tests/parity.rs).
functions / classes / interfacesImplementedUser functions, class with fields/constructors/methods, extends with virtual dispatch, interface / implements with Java 8 default methods, operator overloading.
closures / GStrings / collectionsImplementedFirst-class closures with upvalue capture, GString interpolation, list/map literals, the closure-driven GDK over lists and maps, the spread operator *., first-class ranges.
exceptions / switch / assertImplementedtry/catch/finally/throw with catchable runtime faults, Groovy isCase switch as a statement and as an expression (both the case L -> v arrow form and the yield-carrying colon form), power-assert rendering.
trait, overloading by parameter typePlannedSee BUGS.md for the honest ledger of what is still missing.

Dependency posture

Dependencies are kept foundational and durable — the goal is a crate that still builds cleanly years from now. Direct dependencies from Cargo.toml:

CrateRole
fusevmLanguage-agnostic bytecode VM + three-tier Cranelift JIT (jit, jit-disk-cache, aot)
thiserrorError type derivation
lsp-server / lsp-typesLSP transport + protocol types (--lsp server)
serde / serde_jsonDAP JSON-RPC framing and LSP payloads
libcDAP pipe + dup2 to capture debuggee stdout; language-server orphan guard (getppid)

Compatibility & longevity

Reference semantics

Behaviour tracks Apache Groovy — BigDecimal division, Groovy value printing, String + — as the compatibility target, verified byte-for-byte on the parity corpus.

Cross-architecture

macOS aarch64 and Linux x86_64 / aarch64 via the Cranelift JIT; portable bytecode underneath.

No JVM

Unlike Apache Groovy, groovyrs has no JVM dependency — the runtime is a single native binary.

Standalone crate

An explicit empty [workspace] keeps groovyrs buildable on its own, independent of the meta repo.


Links