eval method

dynamic eval(
  1. String expression
)

Evaluates an expression or statement in the context of previously executed code.

This method allows you to execute additional code in the same environment as a previous execute() call, similar to a REPL experience.

Important: You must call execute() at least once before calling eval() to establish the execution context.

expression The Dart expression or statement to evaluate.

Example:

final d4rt = D4rt();

// First, set up the context
d4rt.execute(source: '''
  var counter = 0;
  void increment() { counter++; }
  int getCounter() => counter;
''', name: 'getCounter');

// Now use eval to interact with the established context
d4rt.eval('increment()');
d4rt.eval('increment()');
final result = d4rt.eval('getCounter()'); // Returns 2

// You can also define new functions
d4rt.eval('int double(int x) => x * 2;');
final doubled = d4rt.eval('double(counter)'); // Returns 4

Implementation

dynamic eval(String expression) {
  if (_visitor == null || !_hasExecutedOnce) {
    throw RuntimeD4rtException(
      'eval() requires an existing execution context. Call execute() first.',
    );
  }

  Logger.debug("[D4rt.eval] Evaluating: $expression");
  final executionEnvironment = _moduleLoader.globalEnvironment;

  // First, try to parse as a top-level declaration (function, class, variable)
  final declarationParseResult = parseString(
    content: expression,
    throwIfDiagnostics: false,
    featureSet: FeatureSet.fromEnableFlags2(
      sdkLanguageVersion: Version(3, 10, 0),
      flags: [
        'non-nullable',
        'null-aware-elements',
        'triple-shift',
        'spread-collections',
        'control-flow-collections',
        'extension-methods',
        'extension-types',
        'digit-separators',
      ],
    ),
  );

  // Check if it parses as valid declaration(s)
  final declErrors = declarationParseResult.errors
      .where((e) => e.diagnosticCode.severity == DiagnosticSeverity.ERROR)
      .toList();

  if (declErrors.isEmpty &&
      declarationParseResult.unit.declarations.isNotEmpty) {
    // It's a declaration - process it directly in the global environment
    final compilationUnit = declarationParseResult.unit;

    // Declaration pass
    final declarationVisitor = DeclarationVisitor(executionEnvironment);
    for (final declaration in compilationUnit.declarations) {
      declaration.accept<void>(declarationVisitor);
    }

    // Interpretation pass — RC-4: ordered by type to handle forward references
    for (final declaration in compilationUnit.declarations) {
      if (declaration is EnumDeclaration) {
        declaration.accept<Object?>(_visitor!);
      }
    }
    // Bug-43 / forward-class-reference FIX (mirrors above).
    _visitor!.deferStaticFieldInits = true;
    try {
      for (final declaration in compilationUnit.declarations) {
        if (declaration is ClassDeclaration ||
            declaration is MixinDeclaration) {
          declaration.accept<Object?>(_visitor!);
        }
      }
    } finally {
      _visitor!.deferStaticFieldInits = false;
    }
    _visitor!.runDeferredStaticInitializers();
    for (final declaration in compilationUnit.declarations) {
      if (declaration is! EnumDeclaration &&
          declaration is! ClassDeclaration &&
          declaration is! MixinDeclaration) {
        declaration.accept<Object?>(_visitor!);
      }
    }

    Logger.debug("[D4rt.eval] Processed declaration(s)");
    return null;
  }

  // Check if this looks like multiple statements (contains ; followed by more code)
  // This heuristic helps us choose the right wrapper: statements vs expression
  final trimmedExpr = expression.trim();
  final looksLikeMultiStatement = RegExp(r';\s*\S').hasMatch(trimmedExpr);

  // For single expressions, try wrapping with return to get the value
  if (!looksLikeMultiStatement) {
    final wrappedSource =
        '''
      dynamic __eval__() {
        return $expression;
      }
    ''';

    final parseResult = parseString(
      content: wrappedSource,
      throwIfDiagnostics: false,
      featureSet: FeatureSet.fromEnableFlags2(
        sdkLanguageVersion: Version(3, 10, 0),
        flags: [
          'non-nullable',
          'null-aware-elements',
          'triple-shift',
          'spread-collections',
          'control-flow-collections',
          'extension-methods',
          'extension-types',
          'digit-separators',
        ],
      ),
    );

    if (parseResult.errors.isEmpty) {
      // Execute as expression with return value
      final compilationUnit = parseResult.unit;

      final declarationVisitor = DeclarationVisitor(executionEnvironment);
      for (final declaration in compilationUnit.declarations) {
        declaration.accept<void>(declarationVisitor);
      }

      for (final declaration in compilationUnit.declarations) {
        declaration.accept<Object?>(_visitor!);
      }

      // Call the __eval__ function
      final evalFunc = executionEnvironment.get('__eval__');
      Object? result;
      if (evalFunc is Callable) {
        try {
          result = evalFunc.call(_visitor!, [], {});
        } on InternalInterpreterD4rtException catch (e) {
          if (e.originalThrownValue is RuntimeD4rtException) {
            throw e.originalThrownValue as RuntimeD4rtException;
          }
          throw e.originalThrownValue ?? e;
        }
      }

      final bridgedResult = _bridgeInterpreterValueToNative(result);
      Logger.debug("[D4rt.eval] Result: $bridgedResult");

      if (bridgedResult is Future) {
        return bridgedResult.then(
          (value) => _bridgeInterpreterValueToNative(value),
        );
      }

      return bridgedResult;
    }
  }

  // Try parsing as statement(s) (no return value expected)
  // This is used for multi-statement code or when expression wrapper fails
  final statementSource =
      '''
    void __eval__() {
      $expression
    }
  ''';

  final statementParseResult = parseString(
    content: statementSource,
    throwIfDiagnostics: false,
    featureSet: FeatureSet.fromEnableFlags2(
      sdkLanguageVersion: Version(3, 10, 0),
      flags: [
        'non-nullable',
        'null-aware-elements',
        'triple-shift',
        'spread-collections',
        'control-flow-collections',
        'extension-methods',
        'extension-types',
        'digit-separators',
      ],
    ),
  );

  if (statementParseResult.errors.isEmpty) {
    final compilationUnit = statementParseResult.unit;

    final declarationVisitor = DeclarationVisitor(executionEnvironment);
    for (final declaration in compilationUnit.declarations) {
      declaration.accept<void>(declarationVisitor);
    }

    for (final declaration in compilationUnit.declarations) {
      declaration.accept<Object?>(_visitor!);
    }

    // Call the __eval__ function
    final evalFunc = executionEnvironment.get('__eval__');
    if (evalFunc is Callable) {
      try {
        evalFunc.call(_visitor!, [], {});
      } on InternalInterpreterD4rtException catch (e) {
        if (e.originalThrownValue is RuntimeD4rtException) {
          throw e.originalThrownValue as RuntimeD4rtException;
        }
        throw e.originalThrownValue ?? e;
      }
    }

    Logger.debug("[D4rt.eval] Executed statement");
    return null;
  }

  // All parsing attempts failed
  final errorMessages = declErrors
      .map((e) {
        final location = declarationParseResult.lineInfo.getLocation(
          e.offset,
        );
        return "- ${e.message} (line ${location.lineNumber}, column ${location.columnNumber})";
      })
      .join("\n");
  throw SourceCodeD4rtException(
    'Failed to parse expression:\n$errorMessages',
    expression,
  );
}