Dart 中Eval函数使用探究

807 阅读6分钟

在Javascript中有内置eval,eval函数允许动态执行JScript源代码, 可以执行一段非固定的字符串代码

例子1:

// 传入字符串形式 JS 表达式
console.log(eval('1 + 1')); // 2
console.log(eval('(() => 3)()')); // 3

例子2:

/**
 * 使用 var 声明的变量和 function 声明的函数修改了当前词法作用域。
 * 在 inner 内部创建的变量 a 和函数 fnA 覆盖了外部的变量 a 和函数 fnA。
 * 变量 a 和函数 fnA 在 inner 内没有被提升。
 */
(function () {
  const a = 0;

  function fnA() {
    return 10;
  }

  (function inner() {
    console.log(a); // 0
    console.log(fnA()); // 10
    eval('var a = 1; function fnA () { return 11; };');
    console.log(a); // 1
    console.log(fnA()); // 11
  }());
}());

本文将研究如何在dart中使用eval函数

先来看几个例子

  1. 使用Isolate.spawnUri调用字符串文件

示例代码:

import 'dart:isolate';

void main() async {
  final uri = Uri.dataFromString(
    '''
    import 'dart:isolate';
    void main(List<String >args, SendPort port) {
      print("args:$args");
      // return
      dynamic result = args.first;
      port.send(result);
    }
   ''',
    mimeType: 'application/dart',
  );
  final port = ReceivePort();
  await Isolate.spawnUri(uri, ["1", "2"], port.sendPort);
  final dynamic response = await port.first;
  print(response);
}

Log:

/opt/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/law/Desktop/Test/test_animations/lib/eval_test/isolate.dart
args:[1, 2]
1

Process finished with exit code 0
  1. 使用spanwUri调用dart文件

code_task.dart 文件

import 'dart:isolate';

void main(List<String> args, SendPort port) {
  print("args:$args");
  // return
  dynamic result = args.first;
  port.send(result);
}

同级下的main文件

import 'dart:isolate';

void main() async {
  final port = ReceivePort();
  await Isolate.spawnUri(Uri(path: "./code_task.dart"), ["1", "2"], port.sendPort);
  final dynamic response = await port.first;
  print(response);
}

Log:

/opt/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/law/Desktop/Test/test_animations/lib/eval_test/isolate_dart.dart
args:$args
1

Process finished with exit code 0
  1. 在dart.ui中无法使用Isolate.spawnUri

示例代码

import 'dart:isolate';
import 'package:flutter/material.dart';

void main() async {
  final uri = Uri.dataFromString(
    '''
    import 'dart:isolate';
    void main(List<String >args, SendPort port) {
      print("args:$args");
      // return
      dynamic result = args.first;
      port.send(result);
    }
   ''',
    mimeType: 'application/dart',
  );
  final port = ReceivePort();
  await Isolate.spawnUri(uri, ["1", "2"], port.sendPort);
  final dynamic response = await port.first;
  print(response);

  runApp(const MyApp());

}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp();
  }
}

Log

Launching lib/eval_test/isolate_test.dart on macOS in debug mode...
Building macOS application...
Debug service listening on ws://127.0.0.1:65269/3I97oHekKyE=/ws
Syncing files to device macOS...
[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: IsolateSpawnException: Unable to spawn isolate: Unsupported isolate URI: package:test_animations/eval_test/data:application/dart,    import 'dart:isolate';
    void main(List<String >args, SendPort port) {
      print("args:$args");
      / return
      dynamic result = args.first;
      port.send(result);
    }

dart_spawner:使用Isolate.spawnUri来实现,只能在Dart VM虚拟器内部执行

dart_eval:自己定义字符串解释器,将字符串代码转换成dart代码,很多标准库还没有实现

使用dart_eval

在项目中我需要在dart.ui项目中使用eval,并且在eval中需要调用外部接口。由于官方提供的例子较少,eval又比较冷门,所以我这里根据项目的需求对它进行了探索和试验。以下是测试过程。

  1. dart_eval的example文件,dart_eval_example.dart

该示例介绍了如何在eval中调用外部类,以及如何扩展外部类,其中定义了属性

import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';

// *** Class definitions. ***                                                            //
//
// NOTE: In order to use these within dart_eval, scroll to the end of the file           //
// to see an example of the necessary boilerplate (will be auto-generated in the future) //

class TimestampedTime {
  const TimestampedTime(this.utcTime, {this.timezoneOffset = 0});

  final int utcTime;
  final int timezoneOffset;

}

abstract class WorldTimeTracker {
  WorldTimeTracker();

  TimestampedTime getTimeFor(String country);
}

// *** Main code *** //

void main(List<String> args) {
  final source = '''
    import 'package:example/bridge.dart';
    
    class MyWorldTimeTracker extends WorldTimeTracker {
    
      MyWorldTimeTracker();
      
      static TimestampedTime _currentTimeWithOffset(int offset) {
        return TimestampedTime(DateTime.now().millisecondsSinceEpoch,
          timezoneOffset: offset);
      }
      
      @override
      TimestampedTime getTimeFor(String country) {
        final countries = <String, TimestampedTime> {
          'USA': _currentTimeWithOffset(4),
          'UK': _currentTimeWithOffset(6),
        };
      
        return countries[country];
      }
    }
    
    MyWorldTimeTracker fn(String country) {
      final timeTracker = MyWorldTimeTracker();
      final myTime = timeTracker.getTimeFor(country);
      
      print(country + ' timezone offset: ' + myTime.timezoneOffset.toString() + ' (from Eval!)');
      
      return timeTracker;
    }
  ''';

  // Create a compiler and define the classes' bridge declarations so it knows their structure
  final compiler = Compiler();
  compiler.defineBridgeClasses([$TimestampedTime.$declaration, $WorldTimeTracker$bridge.$declaration]);

  // Compile the source code into a Program containing metadata and bytecode. In a real app, you would likely
  // compile the Eval code separately and output it to a file using program.write(), sharing only bridge classes
  // with a local shared library
  final program = compiler.compile({
    'example': {'main.dart': source}
  });

  // Create a runtime from the compiled program, and register bridge functions for all static methods and constructors.
  // Default constructors use "ClassName." syntax.
  final runtime = Runtime.ofProgram(program)
    ..registerBridgeFunc('package:example/bridge.dart', 'TimestampedTime.', $TimestampedTime.$new)
    ..registerBridgeFunc('package:example/bridge.dart', 'WorldTimeTracker.', $WorldTimeTracker$bridge.$new,
        isBridge: true);

  // Call runtime.setup() after registering all bridge functions
  runtime.setup();

  // Specify some args for the function we're about to call. Except for [int]s, [double]s, [bool]s, and [List]s, use
  // [$Value] wrappers. For named args, specify them in order using null to represent an unspecified arg.
  runtime.args = [$String('USA')];

  // Call the function and cast the result to the desired type
  final timeTracker = runtime.executeLib('package:example/main.dart', 'fn') as WorldTimeTracker;

  // We can now utilize the returned bridge class
  print('UK timezone offset: ${timeTracker.getTimeFor('UK').timezoneOffset} (from outside Eval!)');
}

///////////////////////////////////////////////////////////////////////////////////////////
// *** Start of required boilerplate code. This can be auto-generated in the future. *** //
///////////////////////////////////////////////////////////////////////////////////////////

/// Create a wrapper for [TimestampedTime]. A wrapper is a performant interop solution
/// when you *don't* need the ability to override the class within the dart_eval VM.
///
/// This is a bimodal wrapper as it implements both [TimestampedTime] and [$Instance].
/// Bimodal wrappers can be used as a type argument for eg. a Future, but if you don't need
/// this it may be simpler to implement only [$Instance].
class $TimestampedTime implements TimestampedTime, $Instance {
  /// Create a wrap constructor. We're not implementing the default constructor here, but if you
  /// were to it'd typically be a runtimeOverride() constructor. You can read more details
  /// about runtime overrides on dart_eval's GitHub wiki page for wrappers. The wrap constructor
  /// wraps an underlying instance and inherits from [$Object].
  $TimestampedTime.wrap(this.$value) : _superclass = $Object($value);

  /// Define the compile-time type descriptor as an unresolved type
  static const $type = BridgeTypeRef(BridgeTypeSpec('package:example/bridge.dart', 'TimestampedTime'));

  /// Define the compile-time class declaration and map out all the fields and methods for the compiler.
  static const $declaration = BridgeClassDef(BridgeClassType($type),
      // Specify class constrctors
      constructors: {
        // Define the default constructor with an empty string
        '': BridgeConstructorDef(BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [
          // Parameters using built-in types can use [RuntimeTypes] for the most common types. Others, like
          // Future, may need to use a type spec for 'dart:core'.
          BridgeParameter('utcTime', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType)), false)
        ], namedParams: [
          BridgeParameter('timezoneOffset', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType)), true)
        ]))
      },
      // Specify class fields
      fields: {
        'utcTime': BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType))),
        'timezoneOffset': BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType)))
      },
      // You can also specify methods, getters, and setters here if present.

      // Inform the compiler that this is a wrapper, not a bridge.
      wrap: true);

  /// Define static [EvalCallableFunc] functions for all static methods and constructors. This is for the
  /// default constructor and is what the runtime will use to create an instance of this class.
  static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
    return $TimestampedTime.wrap(TimestampedTime(args[0]!.$value, timezoneOffset: args[1]?.$value ?? 0));
  }

  /// The underlying Dart instance that this wrapper wraps
  @override
  final TimestampedTime $value;

  /// In most cases [$reified] should just return [$value]. However, classes with generics may use
  /// it to fully reify any properties they contain. For example, a dart_eval List will typically be
  /// filled with [$Value] objects, but using [$reified] will convert it to a List of Dart values.
  @override
  TimestampedTime get $reified => $value;

  /// Although not required, creating a superclass field allows you to inherit basic properties from
  /// [$Object], such as == and hashCode.
  final $Instance _superclass;

  /// [$getProperty] is how dart_eval accesses a wrapper's properties and methods, so map them out here. In
  /// the default case, fall back to our [_superclass] implementation. For methods, you would return
  /// a [$Function] with a closure (for simplicity) or a custom [EvalFunction] subclass (for maximum performance).
  @override
  $Value? $getProperty(Runtime runtime, String identifier) {
    switch (identifier) {
      case 'utcTime':
        return $int($value.utcTime);
      case 'timezoneOffset':
        return $int($value.timezoneOffset);
      default:
        return _superclass.$getProperty(runtime, identifier);
    }
  }

  /// Lookup the runtime type ID
  @override
  int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!);

  /// Map out non-final fields with [$setProperty]. We don't have any here, so just fallback to the Object
  /// implementation. (Although there are no settable fields on Object, in the future it will invoke
  /// noSuchMethod() where appropriate).
  @override
  void $setProperty(Runtime runtime, String identifier, $Value value) {
    return _superclass.$setProperty(runtime, identifier, value);
  }

  /// Finally, our standard [TimestampedTime] implementations! Redirect to the wrapped [$value]'s implementation
  /// for all properties and methods. This is only necessary if using a bimodal wrapper.
  @override
  int get timezoneOffset => $value.timezoneOffset;

  @override
  int get utcTime => $value.utcTime;
}

/// Unlike [TimestampedTime], we need to subclass [WorldTimeTracker]. For that, we can use a bridge class!
/// Bridge classes are flexible and in some ways simpler than wrappers, but they have a lot of overhead. Avoid
/// them if possible in performance-sensitive situations.
///
/// Because [WorldTimeTracker] is abstract, we can implement it here. If it were a concrete class you would instead
/// extend it.
class $WorldTimeTracker$bridge with $Bridge<WorldTimeTracker> implements WorldTimeTracker {
  static const _$type = BridgeTypeRef(BridgeTypeSpec('package:example/bridge.dart', 'WorldTimeTracker'));

  /// Define the compile-time class declaration and map out all the fields and methods for the compiler.
  static const $declaration = BridgeClassDef(BridgeClassType(_$type, isAbstract: true),
      constructors: {
        // Even though this class is abstract, we currently need to define the default constructor anyway. This
        // may change in the future.
        '': BridgeConstructorDef(BridgeFunctionDef(returns: BridgeTypeAnnotation(_$type), params: [], namedParams: []))
      },
      methods: {
        'getTimeFor': BridgeMethodDef(BridgeFunctionDef(returns: BridgeTypeAnnotation($TimestampedTime.$type), params: [
          BridgeParameter('country', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType)), false)
        ], namedParams: []))
      },
      // Inform the compiler that this is a bridge, not a wrapper.
      bridge: true);

  /// Define static [EvalCallableFunc] functions for all static methods and constructors. This is for the
  /// default constructor and is what the runtime will use to create an instance of this class.
  static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
    return $WorldTimeTracker$bridge();
  }

  /// [$bridgeGet] works differently than [$getProperty] - it's only called if the Eval subclass hasn't provided
  /// an override implementation.
  @override
  $Value? $bridgeGet(String identifier) {
    // [WorldTimeTracker] is abstract, so if we haven't overridden all of it's methods that's an error.
    // If it were concrete, this implementation would look like [$getProperty] except you'd access fields
    // and invoke methods on 'super'.
    throw UnimplementedError('Cannot get property "$identifier" on abstract class WorldTimeTracker');
  }

  @override
  void $bridgeSet(String identifier, $Value value) {
    /// Same idea here.
    throw UnimplementedError('Cannot set property "$identifier" on abstract class WorldTimeTracker');
  }

  /// In a bridge class, override all fields and methods with [$_invoke], [$_get], and [$_set]. This
  /// is necessary since we may use the overridden VM implementation outside the VM.
  @override
  TimestampedTime getTimeFor(String country) => $_invoke('getTimeFor', [$String(country)]);
}

Log:

/opt/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/law/Downloads/dart_eval-master/example/dart_eval_example.dart
USA timezone offset: 4 (from Eval!)
UK timezone offset: 6 (from outside Eval!)

Process finished with exit code 0
  1. 给dart_eval_example的类添加自定义方法

import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';

class TimestampedTime {
  const TimestampedTime(this.utcTime, {this.timezoneOffset = 0});

  final int utcTime;
  final int timezoneOffset;
}

abstract class WorldTimeTracker {
  WorldTimeTracker();

  TimestampedTime getTimeFor(String country);
}

void main(List<String> args) {
  const source = '''
    import 'package:example/bridge.dart';
    
    class MyWorldTimeTracker extends WorldTimeTracker {
    
      MyWorldTimeTracker();
      
      static TimestampedTime _currentTimeWithOffset(int offset) {
        return TimestampedTime(DateTime.now().millisecondsSinceEpoch,
          timezoneOffset: offset);
      }
      
      @override
      TimestampedTime getTimeFor(String country) {
        final countries = <String, TimestampedTime> {
          'USA': _currentTimeWithOffset(4),
          'UK': _currentTimeWithOffset(6),
        };
      
        return countries[country];
      }
    }
    
    MyWorldTimeTracker fn(String country) {
    
      final timeTracker = MyWorldTimeTracker();
      final myTime = timeTracker.getTimeFor(country);
      print(myTime.utcTime);
      myTime.controlDevice({"a":"123"});
            
      print(country + ' timezone offset: ' + myTime.timezoneOffset.toString() + ' (from Eval!)');
      
      return timeTracker;
    }
  ''';

  // Create a compiler and define the classes' bridge declarations so it knows their structure
  final compiler = Compiler();
  compiler.defineBridgeClasses([$TimestampedTime.$declaration, $WorldTimeTracker$bridge.$declaration]);

  // Compile the source code into a Program containing metadata and bytecode. In a real app, you would likely
  // compile the Eval code separately and output it to a file using program.write(), sharing only bridge classes
  // with a local shared library
  final program = compiler.compile({
    'example': {'main.dart': source}
  });


  // Create a runtime from the compiled program, and register bridge functions for all static methods and constructors.
  // Default constructors use "ClassName." syntax.
  final runtime = Runtime.ofProgram(program)
    ..registerBridgeFunc('package:example/bridge.dart', 'TimestampedTime.', $TimestampedTime.$new)
    ..registerBridgeFunc('package:example/bridge.dart', 'WorldTimeTracker.', $WorldTimeTracker$bridge.$new, isBridge: true);

  // Call runtime.setup() after registering all bridge functions
  runtime.setup();

  // Specify some args for the function we're about to call. Except for [int]s, [double]s, [bool]s, and [List]s, use
  // [$Value] wrappers. For named args, specify them in order using null to represent an unspecified arg.
  runtime.args = [$String('USA')];

  // Call the function and cast the result to the desired type
  final timeTracker = runtime.executeLib('package:example/main.dart', 'fn') as WorldTimeTracker;

  // We can now utilize the returned bridge class
  print('UK timezone offset: ${timeTracker.getTimeFor('UK').timezoneOffset} (from outside Eval!)');
}

class $TimestampedTime implements TimestampedTime, $Instance {

  $TimestampedTime.wrap(this.$value) : _superclass = $Object($value);

  static const $type = BridgeTypeRef(BridgeTypeSpec('package:example/bridge.dart', 'TimestampedTime'));

  static const $declaration = BridgeClassDef(BridgeClassType($type),
      constructors: {
        '': BridgeConstructorDef(BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [
          BridgeParameter('utcTime', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType)), false)
        ], namedParams: [
          BridgeParameter('timezoneOffset', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType)), true)
        ]))
      },
      fields: {
        'utcTime': BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType))),
        'timezoneOffset': BridgeFieldDef(BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.intType)))
      },
      methods: {
        'controlDevice': BridgeMethodDef(BridgeFunctionDef(returns: BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.nullType)), params: [
          BridgeParameter('args', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.dynamicType), nullable: true), false)
        ], namedParams: []))
      },
      wrap: true);

  static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
    return $TimestampedTime.wrap(TimestampedTime(args[0]!.$value, timezoneOffset: args[1]?.$value ?? 0));
  }

  @override
  final TimestampedTime $value;

  @override
  TimestampedTime get $reified => $value;

  final $Instance _superclass;

  @override
  $Value? $getProperty(Runtime runtime, String identifier) {
    switch (identifier) {
      case 'utcTime':
        return $int($value.utcTime);
      case 'timezoneOffset':
        return $int($value.timezoneOffset);
      case 'controlDevice':
        return $Function((runtime, target, args) => controlDevice(runtime, target, args));
      default:
        return _superclass.$getProperty(runtime, identifier);
    }
  }

  @override
  int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!);

  @override
  void $setProperty(Runtime runtime, String identifier, $Value value) {
    return _superclass.$setProperty(runtime, identifier, value);
  }

  @override
  int get timezoneOffset => $value.timezoneOffset;

  @override
  int get utcTime => $value.utcTime;

  $Value? controlDevice(Runtime runtime, $Value? target, List<$Value?> args) {
    Map<$Value,$Value>? data = args[0]?.$value;
    Map mapData = {};
    data?.forEach((key, value) {
      mapData[key.$value] = value.$value;
    });
    print("controlDevice $mapData");
    return null;
  }
}

class $WorldTimeTracker$bridge with $Bridge<WorldTimeTracker> implements WorldTimeTracker {
  static const _$type = BridgeTypeRef(BridgeTypeSpec('package:example/bridge.dart', 'WorldTimeTracker'));

  static const $declaration = BridgeClassDef(BridgeClassType(_$type, isAbstract: true),
      constructors: {

        '': BridgeConstructorDef(BridgeFunctionDef(returns: BridgeTypeAnnotation(_$type), params: [], namedParams: []))
      },
      methods: {
        'getTimeFor': BridgeMethodDef(BridgeFunctionDef(
            returns: BridgeTypeAnnotation($TimestampedTime.$type),
            params: [BridgeParameter('country', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.stringType)), false)],
            namedParams: []))
      },
      // Inform the compiler that this is a bridge, not a wrapper.
      bridge: true);

  static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
    return $WorldTimeTracker$bridge();
  }

  @override
  $Value? $bridgeGet(String identifier) {
    throw UnimplementedError('Cannot get property "$identifier" on abstract class WorldTimeTracker');
  }

  @override
  void $bridgeSet(String identifier, $Value value) {
    throw UnimplementedError('Cannot set property "$identifier" on abstract class WorldTimeTracker');
  }

  @override
  TimestampedTime getTimeFor(String country) => $_invoke('getTimeFor', [$String(country)]);
}

Log:

/opt/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/law/Downloads/dart_eval-master/tryEval/example_add_function.dart
1683533203985
controlDevice {a: 123}
USA timezone offset: 4 (from Eval!)
UK timezone offset: 6 (from outside Eval!)

Process finished with exit code 0
  1. 模仿example文件自定义class

import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';

const appName = 'evalApp';
const libraryName = 'package:evalApp/evalFunctions.dart';
const fileName = 'evalMain.dart';

void main() {
  const source = '''
import 'package:evalApp/evalFunctions.dart';
  
  class Cat {
  Cat(this.name);
  final String name;
  String speak() {
    return name;
  }
}

  String main() {

    final cat = Cat('Fluffy');
    print("main");
    Fns myF = Fns();
    myF.controlDevice({"a":"123"});
    return cat.speak();
}
  ''';

  //
  final compiler = Compiler();
  compiler.defineBridgeClass($Fns.$declaration);
  //
  final program = compiler.compile({
    appName: {fileName: source}
  });
  final runtime = Runtime.ofProgram(program);
  runtime.registerBridgeFunc(libraryName, 'Fns.', $Fns.$new);
  //
  runtime.setup();

  //
  runtime.executeLib('package:$appName/$fileName', 'main');
}

class Fns {
  static controlDevice(Map data) {
    print("controlDevice:$data");
  }

  static controlScene(Map data) {
    print("controlScene:$data");
  }

  static setVar(Map data) {
    print("setVar:$data");
  }

  static dynamic getVar(Map data) {
    print("getVar:$data");
    return "exampleR";
  }
}

class $Fns implements Fns, $Instance {
  //
  static const $type = BridgeTypeRef(BridgeTypeSpec(libraryName, 'Fns'));
  static const $declaration = BridgeClassDef(BridgeClassType($type),
      constructors: {'': BridgeConstructorDef(BridgeFunctionDef(returns: BridgeTypeAnnotation($type), params: [], namedParams: []))},
      methods: {
        'controlDevice': BridgeMethodDef(BridgeFunctionDef(
            returns: BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.nullType)),
            params: [BridgeParameter('args', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.dynamicType), nullable: true), false)],
            namedParams: []))
      },
      wrap: true);
  static $Value? $new(Runtime runtime, $Value? target, List<$Value?> args) {
    return $Fns.wrap(Fns());
  }

  //
  $Fns.wrap(this.$value) : _superclass = $Object($value);
  final $Instance _superclass;
  //
  @override
  $Value? $getProperty(Runtime runtime, String identifier) {
    switch (identifier) {
      case 'controlDevice':
        return $Function((runtime, target, args) => controlDevice(runtime, target, args));
      default:
        return _superclass.$getProperty(runtime, identifier);
    }
  }

  @override
  int $getRuntimeType(Runtime runtime) => runtime.lookupType($type.spec!);

  @override
  final Fns $value;

  @override
  Fns get $reified => $value;

  @override
  void $setProperty(Runtime runtime, String identifier, $Value value) {
    return _superclass.$setProperty(runtime, identifier, value);
  }

  ///Start  API
  Map _getMapData(List<$Value?> args) {
    Map<$Value, $Value>? data = args[0]?.$value;
    Map mapData = {};
    data?.forEach((key, value) {
      mapData[key.$value] = value.$value;
    });
    return mapData;
  }

  $Value? controlDevice(Runtime runtime, $Value? target, List<$Value?> args) {
    Fns.controlDevice(_getMapData(args));
    return null;
  }
///End  API
}

Log:

/opt/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/law/Downloads/dart_eval-master/tryEval/example_replace_class.dart
main
controlDevice:{a: 123}

Process finished with exit code 0
  1. 使用Eval Plugin,定义自己的eval接口类型

import 'dart:math';

import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/stdlib/core.dart';

const libraryName = 'package:evalApp/evalFunctions.dart';
const appName = 'evalApp';
const fileName = 'evalMain.dart';

const source = '''
  import 'dart:math';
  import 'package:evalApp/evalFunctions.dart';
  class Cat {
  Cat(this.name);
  final String name;
  String speak() {
    return name;
  }
}

  String main() {
    double a = tan(20);
    print(a);
    controlDevice({"1":"2"});
    final cat = Cat('Fluffy');
    print("main");
    return cat.speak();
}
  ''';

void main() {

  DateTime dateStart = DateTime.now();
  // 制作插件
  MyAppPlugin myAppPlugin = MyAppPlugin();
  // 定义 compile
  final compiler = Compiler();
  compiler.addPlugin(myAppPlugin);
  // 定义 program
  final program = compiler.compile({
    appName: {fileName: source}
  });
  // 定义 runtime
  final runtime = Runtime.ofProgram(program);
  runtime.addPlugin(myAppPlugin);
  runtime.setup();
  //
  runtime.executeLib('package:$appName/$fileName', 'main');

  print("main end:${DateTime.now().difference(dateStart)}");

}


void anotherMain(){
  DateTime dateStart = DateTime.now();
  MyAppPlugin myAppPlugin = MyAppPlugin();
  eval(source,plugins: [myAppPlugin]);
  print("main end:${DateTime.now().difference(dateStart)}");
}

class Fns {
  static controlDevice(Map data) {
    print("controlDevice:$data");
  }

  static controlScene(Map data) {
    print("controlScene:$data");
  }

  static setVar(Map data) {
    print("setVar:$data");
  }

  static dynamic getVar(Map data) {
    print("getVar:$data");
    return "exampleR";
  }
}

class MyAppPlugin implements EvalPlugin {
  @override
  void configureForCompile(BridgeDeclarationRegistry registry) {
    print("configureForCompile");
    registry.defineBridgeTopLevelFunction(const BridgeFunctionDeclaration(
        libraryName,
        'controlDevice',
        BridgeFunctionDef(
            returns: BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.nullType)),
            params: [BridgeParameter('args', BridgeTypeAnnotation(BridgeTypeRef.type(RuntimeTypes.mapType)), false)])));
  }

  @override
  void configureForRuntime(Runtime runtime) {
    print("configureForRuntime");
    runtime.registerBridgeFunc(libraryName, 'controlDevice', const _$controlDevice());
  }

  @override
  String get identifier => libraryName;

}

mixin ArgsMap {
  Map _getMapData(List<$Value?> args) {
    Map<$Value, $Value>? data = args[0]?.$value;
    Map mapData = {};
    data?.forEach((key, value) {
      mapData[key.$value] = value.$value;
    });
    return mapData;
  }
}

class _$controlDevice with ArgsMap implements EvalCallable {
  const _$controlDevice();

  @override
  $Value? call(Runtime runtime, $Value? target, List<$Value?> args) {
    Fns.controlDevice(_getMapData(args));
    return const $null();
  }
}

Log:

/opt/flutter/bin/cache/dart-sdk/bin/dart --enable-asserts /Users/law/Downloads/dart_eval-master/tryEval/example_custom.dart
configureForCompile
configureForRuntime
2.2371609442247427
controlDevice:{1: 2}
main
main end:0:00:00.237323

Process finished with exit code 0

daer_eval实现的库和支持的特性如下

import 'dart:core';
import 'dart:math';
import 'dart:async';
import 'dart:core';
import 'dart:convert';
import 'dart:io';

下表详细介绍了带有原生Dart代码的dart_eval支持的语言功能。桥接时,功能支持可能会有所不同。

FeatureSupport levelTests
Imports[1][2][3]
Exports[1][2]
part / part of[1]
show and hide[1]
Conditional importsN/A
Deferred importsN/A
Functions[1]
Anonymous functions[1][2][3][4][5]
Arrow functions[1][2]
Sync generatorsN/A
Async generatorsN/A
Tear-offsPartial[1][2]
For loops[1][2]
While loops
Do-while loops
For-each loops[1]
Async for-eachN/A
Switch statementsN/A
Labels and breakN/A
If statements[1]
Try-catchPartial[1][2]
Try-catch-finallyN/A
Lists[1]
Iterable[1][2]
MapsPartial
SetsN/A
Collection for[1][2]
Collection if[1][2]
SpreadsN/A
Classes[1]
Class static methods[1][2]
Getters and setters[1]
Factory constructorsN/A
new keyword[1]
Class inheritance[1]
Abstract and implementsPartial
this keyword[1][2]
super keyword
Super constructor params[1]
MixinsN/A
FuturesPartial[1][2]
Async/await[1][2][3]
StreamsPartial[1]
String interpolation[1]
EnumsN/A
Generic function typesPartial[1]
TypedefsN/A
Generic classesPartial
Type tests (is)[1]
Casting (as)N/A
assertN/A
Null safetyPartial
Late initializationN/A
Cascades
Ternary expressions[1]
Extension methodsN/A
Const expressionsPartialN/A
IsolatesN/A

All Done!