tom_d4rt 1.10.1 copy "tom_d4rt: ^1.10.1" to clipboard
tom_d4rt: ^1.10.1 copied to clipboard

D4rt - A Dart interpreter and runtime with bridging, security sandboxing, and dynamic code execution. Fork of D4rt with extended features.

tom_d4rt #

Attribution. The tom_d4rt project is an extended clone of the original d4rt project by Moustapha Kodjo Amadou, initially published in 2025. The complete interpreter is based on his idea.

A secure, sandboxed Dart interpreter written in Dart — the analyzer-based reference implementation of the D4rt runtime.

Overview #

tom_d4rt executes Dart source code at runtime without compilation. It is built on top of the analyzer package, which provides a full parse tree (AST) for any Dart 3 source string. The interpreter walks that AST in two passes — a declaration pass that registers classes and mixins as placeholders, followed by an interpretation pass that evaluates imports, resolves members, and calls the target function.

Two execution modes are supported:

  • execute() — full-script, two-pass execution. Parses the source, runs both the DeclarationVisitor (pass 1) and InterpreterVisitor (pass 2), then calls the named entry point. Each call gets a fresh module loader and global environment.
  • eval() — REPL-style incremental evaluation. Reuses the InterpreterVisitor and Environment from the last execute() call, so previously defined variables and functions are still in scope.

Scripts run inside an isolated Environment scope chain. Sensitive operations — dart:io, dart:isolate, process execution, network access — are blocked by default. The host process grants fine-grained permissions via the grant() API.

tom_d4rt is the stable reference that all existing projects in this workspace depend on. Its public API (D4rt, BridgedClass, BridgedEnumDefinition, D4, and the permission classes) is kept in 1:1 lockstep with the analyzer-free successor line (tom_d4rt_ast) so that bridge code generated by tom_d4rt_generator works against either interpreter without modification.

Source-based vs analyzer-free — which line to use #

D4rt ships in two execution families that share one bridge API:

tom_ast_model  ←  tom_d4rt_ast  ←  tom_ast_generator  ←  tom_d4rt_exec  ←  tom_dcli_exec

tom_d4rt is the stable reference and is usually the preferable choice. The analyzer-free line exists for the one case the analyzer cannot serve: the web (where the analyzer package is too large to ship) and on-the-fly / OTA UI updates, where a server compiles source to an AST bundle that a thin client interprets without redeploying. It is otherwise a complete alternative, but because the generated AST bundles are large, reach for it only when that web/OTA constraint actually applies.

tom_d4rt_exec is the analyzer-free counterpart of this package: a drop-in D4rt-style entry point that parses with the analyzer at build time and interprets via tom_d4rt_ast at run time. tom_d4rt_ast shares this package's bridge API surface, and both lines are kept in sync — any fix merged into tom_d4rt is back-ported to tom_d4rt_ast and vice versa.

Installation #

dependencies:
  tom_d4rt: ^1.8.23
dart pub add tom_d4rt

Requires Dart SDK ^3.5.0. The only runtime dependencies are analyzer: ^8.0.0 and pub_semver: ^2.2.0.

Features #

Language coverage #

tom_d4rt passes all 20 areas of the Dart language overview test suite (1,680+ tests). Covered language constructs include:

Area Details
Classes Declarations, constructors (factory, named, redirecting, const), inheritance, super, abstract/final/sealed/interface/base class modifiers
Mixins with, mixin abstract method satisfaction, enum-with-mixin
Generics Generic classes and functions, type bounds, variance, F-bounded polymorphism, is checks with parameterized types
Patterns Destructuring, switch expressions and statements, logical-OR patterns, when guards, record patterns (named fields, shorthand :name)
Records Positional and named fields (up to 9 positional fields returned as native Dart records; larger records return InterpretedRecord)
Async/await async/await, async* generators, sync* generators, await for, Future, Stream, StreamController, StreamTransformer, Timer, Completer
Extensions Instance and static extension members, extensions on bridged types, imported extensions
Extension types Dart 3.3+ inline classes / extension types
Enums Enhanced enums with members, enums with mixins, .name, .index, .values, .byName
Error handling try/catch/finally, rethrow, custom exception classes
Operators All arithmetic, comparison, bitwise, cascade (.., ?..), spread, null-aware, type (is, as, is!)
late variables Lazy initialization, late final, static and instance late fields, LateInitializationError
Control flow if/else, for, for-in, while, do-while, switch, break/continue with labels
Collections List, Set, Map, spread operator, collection-if / collection-for, const collections
Null safety Full null-safety — nullable types, null-aware operators (?., ??, ??=, !)
const Const expressions, const constructors, const fields, const collections
Typedefs Function and type typedefs
Annotations Declaration annotations
Libraries import/export with show/hide, relative imports, multi-file source maps, file-system imports
Isolates Isolate, SendPort, ReceivePort, Capability (communication bridged; spawning interpreted closures across isolate boundaries is a known limitation)

Standard library bridges #

The following dart: libraries are bridged out of the box:

Library Key types
dart:core int, double, num, bool, String, StringBuffer, List, Map, Set, Iterable, Iterator, DateTime, Duration, RegExp, Uri, BigInt, Symbol, Runes, Enum, Function, Type, StackTrace, Error, and all standard exceptions
dart:async Future, Stream, StreamController, StreamSubscription, StreamTransformer, Completer, Timer
dart:collection HashMap, HashSet, LinkedHashMap, LinkedList, ListQueue, Queue, SplayTreeMap, UnmodifiableListView
dart:convert jsonEncode/jsonDecode, base64Encode/base64Decode, utf8, ascii, latin1, LineSplitter, HtmlEscape, codecs and converters
dart:math min, max, sqrt, pow, log, sin, cos, tan, Random, constants (pi, e, ln2, …)
dart:typed_data Uint8List, Int16List, Float32List, ByteData, ByteBuffer, Endian — eagerly registered so Flutter bridge code that uses ByteData without an explicit import works out of the box
dart:io File, Directory, HttpClient, Socket, Process, IOSink, Stdin/Stdout — available only after grant(FilesystemPermission.any) / grant(NetworkPermission.any)
dart:isolate Available only after grant(IsolatePermission.any)

External dart: URIs not in the list above are checked for registered bridge content before raising an error, allowing embedding projects to supply bridges for dart:ui or other platform libraries.

Bridging native code #

Any native Dart class, enum, top-level function, or variable can be exposed to interpreted scripts by registering it before execution. The tom_d4rt_generator package automates bridge creation from annotated source files.

Permission sandboxing #

Scripts run in a deny-by-default sandbox. The host process grants and revokes permissions at any granularity.

Quick start #

import 'package:tom_d4rt/tom_d4rt.dart';

void main() {
  final d4rt = D4rt();

  // Execute a script — calls main() by default
  d4rt.execute(
    source: '''
      void main() {
        print('Hello from D4rt!');
      }
    ''',
  );

  // Call a named function with arguments
  final result = d4rt.execute(
    source: '''
      String greet(String name, int age) {
        return 'Hello \$name, you are \$age';
      }
    ''',
    name: 'greet',
    positionalArgs: ['Alice', 30],
  );
  print(result); // Hello Alice, you are 30
}

Example projects #

Runnable, self-contained samples live in tom_d4rt_samples/. They are ordered as a learning path — each one introduces exactly one new capability on top of the last:

Sample What it teaches Interpreter
d4rt_introduction_sample Run multi-file D4rt programs with nothing but the interpreter — no bridges, no host wiring. Includes shebang launchers (#!/usr/bin/env run_example). Start here. tom_d4rt
d4rt_advanced_sample Cross the first boundary: bridge a native Dart library into scripts using tom_d4rt_generator. tom_d4rt
d4rt_userbridges_sample Hand-write D4UserBridge overrides for what the generator can't bridge automatically. tom_d4rt
d4rt_dcli_sample Shell scripting plus your own bridges via the tom_d4rt_dcli REPL. tom_d4rt
d4rt_flutter_sample Interpret a live Flutter UI from source at runtime. tom_d4rt_flutter

If you are new to D4rt, clone the introduction sample and run one of its examples directly — it is the shortest path from "installed" to "running a script".

Usage #

Full-script execution #

execute() always initializes a fresh ModuleLoader and Environment. Every call is independent unless you explicitly use continuedExecute() or eval().

final d4rt = D4rt();

// With named and positional arguments
final result = d4rt.execute(
  source: '''
    String greet({required String name, int times = 1}) {
      return List.generate(times, (_) => 'Hello \$name').join(', ');
    }
  ''',
  name: 'greet',
  namedArgs: {'name': 'World', 'times': 3},
);
// 'Hello World, Hello World, Hello World'

// Multi-file execution via a source map
d4rt.execute(
  source: '''
    import 'package:my_app/utils.dart';
    void main() {
      print(formatDate(DateTime.now()));
    }
  ''',
  sources: {
    'package:my_app/utils.dart': '''
      String formatDate(DateTime d) => '\${d.year}-\${d.month}-\${d.day}';
    ''',
  },
);

// File-system imports (requires filesystem permission)
d4rt.grant(FilesystemPermission.read);
d4rt.execute(
  source: "import './lib/utils.dart'; void main() { helper(); }",
  basePath: '/path/to/project',
  allowFileSystemImports: true,
);

continuedExecute() reuses the existing environment from a prior execute() call, letting you add declarations without resetting state.

REPL-style evaluation #

After calling execute() to establish a context, eval() evaluates expressions and statements incrementally in the same environment:

final d4rt = D4rt();

d4rt.execute(source: '''
  var counter = 0;
  void increment() { counter++; }
''');

d4rt.eval('increment()');
d4rt.eval('increment()');
print(d4rt.eval('counter')); // 2

// Define a new function in the same session
d4rt.eval('int doubled() => counter * 2;');
print(d4rt.eval('doubled()')); // 4

eval() first tries to parse the string as a top-level declaration. If that succeeds, it registers the declaration and returns null. Otherwise it wraps the string in dynamic __eval__() { return <expr>; } and executes it, returning the value.

To reset the session between REPL interactions without rebuilding the bridge registrations, call resetScriptDeclarations().

Code introspection #

analyze() parses source and returns an IntrospectionResult describing all top-level declarations without executing any function:

final result = d4rt.analyze(source: '''
  class Person {
    final String name;
    final int age;
    Person(this.name, this.age);
    String greet() => "Hi, I'm \$name";
  }

  int add(int a, int b) => a + b;
  final greeting = 'Hello';
''');

print(result.classes);   // [ClassInfo(Person)]
print(result.functions); // [FunctionInfo(add)]
print(result.variables); // [VariableInfo(greeting)]

Registering bridged classes #

A BridgedClass adapts a native Dart class for use inside interpreted scripts. Constructors, instance methods, instance getters/setters, static methods, and static getters/setters are all expressed as Dart closures with a standard adapter signature.

import 'package:tom_d4rt/tom_d4rt.dart';

class Counter {
  int value;
  Counter(this.value);
  void increment() => value++;
  void add(int n) => value += n;
}

void main() {
  final d4rt = D4rt();

  final counterBridge = BridgedClass(
    nativeType: Counter,
    name: 'Counter',
    constructors: {
      // Default constructor — named '' for the unnamed constructor
      '': (visitor, positional, named) => Counter(positional[0] as int),
    },
    getters: {
      'value': (visitor, target) => (target as Counter).value,
    },
    setters: {
      'value': (visitor, target, v) => (target as Counter).value = v as int,
    },
    methods: {
      'increment': (visitor, target, positional, named, typeArgs) {
        (target as Counter).increment();
        return null;
      },
      'add': (visitor, target, positional, named, typeArgs) {
        (target as Counter).add(positional[0] as int);
        return null;
      },
    },
  );

  d4rt.registerBridgedClass(counterBridge, 'package:my_app/counter.dart');

  final result = d4rt.execute(source: '''
    import 'package:my_app/counter.dart';

    int main() {
      final c = Counter(10);
      c.increment();
      c.add(5);
      return c.value;  // 16
    }
  ''');

  print(result); // 16
}

Additional registration methods on D4rt:

Method Purpose
registerBridgedEnum(def, library) Expose a native enum to scripts
registerBridgedExtension(def, library) Expose a Dart extension to scripts
registertopLevelFunction(name, fn, library) Expose a top-level function
registerGlobalVariable(name, value, library) Expose a top-level variable (eager)
registerGlobalGetter(name, getter, library) Expose a top-level variable (lazy, evaluated on access)
registerGlobalSetter(name, setter, library) Expose a top-level setter
registerClassAlias(alias, target, library) Register a typedef-style class alias
registerFunctionTypedef(name, library) Register a function typedef name for type resolution
registerLibraryReExport(source, target, {show, hide}) Mirror export directives so scripts that import a barrel get all re-exported symbols
registerExtensions(packageName, body) Queue a post-registration callback for relaxers / proxy factories
finalizeBridges() Run all queued extension callbacks (called automatically on first execute/eval)
registerRelaxerFactory(baseTypeName, factory) Register a relaxer that coerces interpreted values into a native parameterized bridged type
registerInterfaceProxy(bridgedTypeName, factory) Register a proxy so an interpreted instance can satisfy a bridged abstract interface
registerGenericConstructor(className, ctorName, factory) Register a factory that builds a native generic bridged instance from interpreted args + type args
warmup() Finalize bridges and JIT-warm the parser/interpreter with a throwaway build

The three facades (registerRelaxerFactory / registerInterfaceProxy / registerGenericConstructor) are thin wrappers over the static D4 registries, meant to be called from inside a registerExtensions body so they run once at finalize time, in package order, after the standard bridges are wired up. See the User Guide → Extension Registration and Facades for the full contract. Set D4.usageLogEnabled = true (or the env var D4RT_LOG_RELAXER_USAGE) to audit which relaxers/proxies are hit at runtime.

For large bridge surfaces, use tom_d4rt_generator to generate all adapter boilerplate from annotated native source. Duplicated registrations are safely deduplicated via sourceUri tracking.

For the complete registration reference see the Bridging Guide; for a worked end-to-end example, the d4rt_advanced_sample bridges a native library with the generator, and the d4rt_userbridges_sample shows hand-written overrides.

Permission system #

All sensitive operations are blocked by default. Grant permissions before executing code that needs them:

final d4rt = D4rt();

// Filesystem
d4rt.grant(FilesystemPermission.read);             // read any path
d4rt.grant(FilesystemPermission.writePath('/tmp')); // write under /tmp only
d4rt.grant(FilesystemPermission.any);              // read + write + execute, any path

// Network
d4rt.grant(NetworkPermission.connectTo('api.example.com'));
d4rt.grant(NetworkPermission.listenOn(8080));
d4rt.grant(NetworkPermission.any);

// Process execution
d4rt.grant(ProcessRunPermission.command('git'));
d4rt.grant(ProcessRunPermission.any);

// Isolates
d4rt.grant(IsolatePermission.spawn);
d4rt.grant(IsolatePermission.any);

// Dangerous (use with extreme caution)
d4rt.grant(DangerousPermission.codeEvaluation);
d4rt.grant(DangerousPermission.nativePlugins);

Permissions can be revoked at any time with d4rt.revoke(permission). Check the current set with d4rt.hasPermission(permission) or d4rt.checkPermission(operation).

The full permission class hierarchy:

Class Static constants Factory constructors
FilesystemPermission .read, .write, .execute, .any .readPath(p), .writePath(p), .executePath(p), .path(p)
NetworkPermission .connect, .listen, .bind, .any .connectTo(host), .connectToPort(host, port), .listenOn(port)
ProcessRunPermission .any .command(cmd), .commandWithArgs(cmd, args)
IsolatePermission .spawn, .communicate, .any
DangerousPermission .codeEvaluation, .nativePlugins, .any

D4 bridge helper #

The D4 class provides static utilities used in generated and hand-written bridge adapters:

  • Type coercion: D4.coerceList<T>(raw), D4.coerceMap<K,V>(raw)
  • Argument extraction: D4.getRequiredArg<T>(positional, index, name, className), D4.getOptionalArg<T>(...), D4.getNamedArg<T>(named, name, className)
  • Target validation: D4.validateTarget<T>(target, className) — ensures the receiver is of the expected native type before calling instance methods
  • Arity checking: D4.checkArity(positional, expected, methodName)
  • Callback bridging: D4.callInterpreterCallback(visitor, fn, args) — dispatches a call from a native callback into the interpreter
  • Active visitor: D4.withActiveVisitor(visitor, body) — sets the thread-local active visitor used by interface-proxy factories
  • Generic wrappers: D4.registerGenericTypeWrapper<T>(factory), D4.extractBridgedArg<T>(raw, visitor) for covariant generic type resolution

Configuration inspection #

d4rt.getConfiguration() returns a D4rtConfiguration snapshot listing all registered bridges, granted permissions, global variables and getters, and global functions. d4rt.getEnvironmentState() returns an EnvironmentState with the names of variables, bridged classes, and bridged enums currently live in the global scope.

d4rt.validateRegistrations(source: ...) runs a full parse and import pass in error-collection mode, returning a list of registration conflict messages without aborting on the first error.

Architecture #

Two-pass execution #

Source string
    │
    ▼
 analyzer.parseString()  ──►  CompilationUnit (AST)
    │
    ├─ Pass 1: DeclarationVisitor
    │     Registers InterpretedClass / InterpretedMixin placeholders
    │     in the global Environment. No member resolution yet.
    │
    └─ Pass 2: InterpreterVisitor
          1. ImportDirectives  → ModuleLoader loads bridge libraries
          2. EnumDeclarations  → populate enum value tables
          3. ClassDeclarations → populate constructors, methods, fields
             (static field inits deferred to avoid forward-ref issues)
          4. ExtensionDeclarations / ExtensionTypeDeclarations
          5. FunctionDeclarations
          6. TopLevelVariableDeclarations
          7. Call named entry point

Environment #

Environment is a lexical scope chain backed by a Map<String, Object?>. Each function call or block creates a child Environment that delegates lookups to its enclosing scope. The global environment holds stdlib bridges, user-registered bridges, and top-level script declarations. InterpreterVisitor carries a reference to the current Environment and updates it as it walks the AST.

Bridging system #

A BridgedClass wraps a native Dart type. When the interpreter encounters new MyNative() or a method call on a BridgedInstance, it looks up the registered BridgedClass and invokes the corresponding adapter closure. The adapter receives an InterpreterVisitor (for re-entering the interpreter from callbacks), the native target object, and the evaluated argument lists.

BridgedInstance wraps a native value and carries a reference to its BridgedClass. It exposes get, set, and call dispatch and handles all Dart operators (+, -, [], []=, ==, etc.).

BridgedEnumDefinition exposes native Enum values, including .name, .index, .values, and .byName, as well as custom methods and getters.

BridgedExtensionDefinition exposes extension methods so they are discoverable by Environment.findExtensionMember.

Module loader #

ModuleLoader manages a map of package URI to source string. When an import directive is processed, it parses the target source, runs pass 1 and pass 2 in that module's own Environment, and merges the exported names (respecting show/hide) into the importing scope. Bridged libraries are registered into a per-module environment without requiring a source string. registerLibraryReExport lets bridge packages model export directives so transitive re-exports are automatically resolved.

Key types exposed from package:tom_d4rt/tom_d4rt.dart #

Type Role
D4rt Main interpreter class — entry point for all execution
InterpreterVisitor AST visitor that drives interpretation; accessible via d4rt.visitor
DeclarationVisitor Pass-1 AST visitor that seeds class placeholders
Environment Lexical scope chain
BridgedClass Adapter descriptor for a native class
BridgedInstance Runtime wrapper around a native object
BridgedEnumDefinition Adapter descriptor for a native enum
BridgedExtensionDefinition Adapter descriptor for a native extension
D4 Static helpers for generated bridge adapters
Permission (and subclasses) Sandbox permission objects
IntrospectionResult Output of d4rt.analyze()
ScriptExecutionResult Structured result from file-based script execution
D4rtConfiguration Snapshot of all registered bridges and permissions
RuntimeD4rtException Thrown for interpreter-level errors
SourceCodeD4rtException Thrown for parse errors in the source
LibraryVariable, LibraryGetter, LibrarySetter, LibraryFunction, LibraryClass, LibraryEnum, LibraryExtension Wrappers used when registering bridge elements under a library URI

Ecosystem #

tom_d4rt  (this package)
   │
   ├─ tom_d4rt_generator
   │     Source-generator for BridgedClass / BridgedEnumDefinition boilerplate.
   │     Reads @D4rtUserBridge annotations, emits bridge .dart files.
   │     See: ../tom_d4rt_generator/doc/bridgegenerator_user_guide.md
   │
   └─ tom_d4rt_dcli  (tom_dcli)
         DCli-based CLI runner that uses tom_d4rt to execute *.dcli.dart scripts.

Analyzer-free parallel line (same bridge API, no analyzer dependency):
   tom_ast_model  ←  tom_d4rt_ast  ←  tom_ast_generator  ←  tom_d4rt_exec  ←  tom_dcli_exec

Bridges generated by tom_d4rt_generator (or its AST-line counterpart tom_ast_generator) compile against this package's API. Both generators produce code that calls identical registerBridgedClass / registerBridgedEnum / registerExtensions / finalizeBridges sequences, so a bridge package works against either the tom_d4rt or tom_d4rt_ast interpreter without branching.

Further documentation #

This package's own guides (in doc/):

  • User Guide — execution modes, configuration, multi-file scripts, extension registration & facades
  • Bridging Guide — detailed coverage of every registration API
  • Advanced Bridging Guide — D4 helper class, type coercion, argument extraction, interface proxies, generic constructors
  • Limitationscanonical interpreter limitations reference. Every AST-variant, exec, and Flutter project links back to this file for shared interpreter limits and documents only its own deltas.
  • Issues — tracked interpreter issues and their status

Related packages (don't duplicate — follow the link):

Status #

Stable. Published at 1.8.23 on pub.dev.

The test suite covers 1,680+ tests (all passing; 2 known Won't Fix limitations for records with >9 positional fields and for spawning interpreted closures across isolate boundaries).

Repository: github.com/al-the-bear/tom_d4rt — tom_d4rt

0
likes
140
points
482
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

D4rt - A Dart interpreter and runtime with bridging, security sandboxing, and dynamic code execution. Fork of D4rt with extended features.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

analyzer, pub_semver

More

Packages that depend on tom_d4rt