Guide
Interpreter Architecture
How parser, evaluator, runtime values, dispatch, and package layers fit together
miniR is organized around one rule: interpreter state belongs to the Interpreter instance, not to process-global mutable statics. That rule is what makes the runtime reentrant and embeddable.
If you want the focused explanation of that design rule, read Reentrant Runtime. If you want the operational evaluation path, read The Interpreter. If you want the parser boundary, read Parser And Diagnostics. If you want the public embedding boundary, read Session API And Embedding. This page is the wider map around all of them.
Top-Level Layers
src/session.rsexposesSession, the public API used by tests, embeddings, and the CLI.src/main.rsis intentionally thin. It parses command-line flags, starts the REPL when needed, and delegates real work toSession.src/parser/r.pest,src/parser.rs, andsrc/parser/ast.rsturn source text into an AST.src/interpreter.rsis the tree-walking evaluator and the home of per-interpreter state.
Front End: Parse To AST
src/parser/r.pestdefines the grammar and operator precedence.src/parser.rsconverts pest pairs into AST nodes.src/parser/diagnostics.rsformats parse failures and suggestions when thediagnosticsfeature is enabled.
The parser layer should only answer syntax questions. If a bug is about lazy evaluation, environments, S3 dispatch, or replacement semantics, it almost never belongs here.
The dedicated Parser And Diagnostics page follows that front end in more detail.
Runtime Core
src/interpreter/value.rsdefinesRValue, vectors, lists, functions, promises, language objects, andRError.src/interpreter/environment.rsimplements lexical scoping withRc<RefCell<_>>.src/interpreter.rsstores stdout/stderr, RNG state, temp dirs, env vars, working directory, options, condition handlers, help indexes, and traceback state on the interpreter itself.
That last point is the architectural center of gravity. miniR is not trying to fake reentrancy with a mostly-global runtime plus a few isolated fields. The runtime state that matters is interpreter-owned.
Environment hierarchy follows the usual miniR shape:
| Layer | Purpose |
|---|---|
| Base environment | Builtins and standard bindings |
| Global environment | User workspace |
| Local call environments | Closure calls, promises, and temporary scopes |
Evaluation And Dispatch
src/interpreter/call_eval.rsresolves call targets, creates promises for closures, forces arguments for builtins, and dispatches builtin versus closure calls.src/interpreter/arguments.rsimplements three-pass closure argument matching: exact, partial, then positional.src/interpreter/call.rsdefinesBuiltinContext,CallFrame, and traceback capture.src/interpreter/s3.rshandles S3 generic dispatch withUseMethod()andNextMethod().
This is the layer to change when package code fails because of call semantics, lazy evaluation, argument matching, or traceback behavior.
The dedicated The Interpreter page follows that call path in more detail from Session::eval_source() through promise creation, builtin dispatch, and stack-trace capture.
Semantic Subsystems
src/interpreter/ops.rsimplements arithmetic, comparison, logical operators,%in%, ranges, and matrix operators.src/interpreter/control_flow.rshandlesif, loops,for,repeat, and pipes.src/interpreter/assignment.rsowns assignment and replacement semantics.src/interpreter/indexing.rsowns read-side indexing for vectors, lists, matrices, and data frames.
These files matter more than builtin count when real packages fail. Many CRAN corpus regressions are semantic mismatches in these layers, not missing leaf functions.
Builtins, Packages, Native, And Graphics
src/interpreter/builtins.rswires builtin registration and builtin help synthesis.src/interpreter/builtins/*.rsgroups builtins by domain such as strings, math, system, conditions, datetime, graphics, and native helpers.src/interpreter/packages/handles package loading, namespace behavior, and Rd help indexing.src/interpreter/native/handles compiled code, loading, routine lookup, and native stack unwinding.src/interpreter/graphics/andsrc/interpreter/grid/hold graphics and device state.
Most of these layers are feature gated. The parser and evaluator core are always present; heavy subsystems such as native loading, GUI plotting, TLS, linalg, and parquet are optional.
If you want the registration mechanics behind the builtin layer, Builtin Registry And linkme explains how linkme and minir-macros assemble the builtin registry. If you want the doc lookup side of the package layer, Help And Documentation explains how package man/ pages and builtin rustdoc are indexed together.
Where To Make Changes
| If the bug looks like... | Start here |
|---|---|
| Parse precedence or newline handling | parser/r.pest, parser.rs |
| Wrong call semantics or lazy forcing | call_eval.rs, arguments.rs, call.rs |
| Wrong subset or replacement behavior | indexing.rs, assignment.rs |
| Missing or incorrect builtin | builtins/*.rs |
| Namespace or package-loading issue | packages/, builtins/pre_eval.rs |
Native .Call or C API issue | native/ |
| Traceback or error-reporting issue | call.rs, session.rs, value/error.rs |
The Main Architectural Bet
miniR is not trying to win by putting more logic into process-global runtime state. The bet is the opposite: keep the parser separate, keep state on Interpreter, keep builtin registration declarative, and make heavy subsystems optional behind feature flags. That structure is what lets the project grow without turning into a single giant evaluator file.