GithubHelp home page GithubHelp logo

ethanblake4 / dart_eval Goto Github PK

View Code? Open in Web Editor NEW
299.0 10.0 32.0 3.92 MB

Extensible Dart interpreter for Dart with full interop

Home Page: https://pub.dev/packages/dart_eval

License: BSD 3-Clause "New" or "Revised" License

Dart 100.00%
compiler dart flutter interpreter codepush

dart_eval's Introduction

Build status License: BSD-3 Web example Star on Github

dart_eval is an extensible bytecode compiler and interpreter for the Dart language, written in Dart, enabling dynamic execution and codepush for Flutter and Dart AOT.

dart_eval pub package
flutter_eval pub package
eval_annotation pub package

The primary aspect of dart_eval's goal is to be interoperable with real Dart code. Classes created in 'real Dart' can be used inside the interpreter with a wrapper, and classes created in the interpreter can be used outside it by creating an interface and bridge class.

dart_eval's compiler is powered under the hood by the Dart analyzer, so it achieves 100% correct and up-to-date parsing. While compilation and execution aren't quite there yet, dart_eval has over 200 tests that are run in CI to ensure correctness.

Currently dart_eval implements a majority of the Dart spec, but there are still missing features like generators, Sets and extension methods. In addition, parts of the standard library haven't been implemented. See the language feature support table for details.

Usage

Note: See the README for flutter_eval for information on setting up Flutter code push.

A basic usage example of the eval method, which is a simple shorthand to execute Dart code at runtime:

import 'package:dart_eval/dart_eval.dart';

void main() {
  print(eval('2 + 2')); // -> 4
  
  final program = r'''
      class Cat {
        Cat(this.name);
        final String name;
        String speak() => "I'm $name!";
      }
      String main() {
        final cat = Cat('Fluffy');
        return cat.speak();
      }
  ''';
  
  print(eval(program, function: 'main')); // prints 'I'm Fluffy!'
}

Passing arguments

In most cases, you should wrap arguments you pass to dart_eval in $Value wrappers, such as $String or $Map. These 'boxed types' have information about what they are and how to modify them, and you can access their underlying value with the $value property. However, ints, doubles, bools, and Lists are treated as primitives and should be passed without wrapping when their exact type is specified in the function signature:

final program = '''
  int main(int count, String str) {
    return count + str.length;
  }
''';

print(eval(program, function: 'main', args: [1, $String('Hi!')])); // -> 4

When calling a function or constructor externally, you must specify all arguments - even optional and named ones - in order, using null to indicate the absence of an argument (whereas $null() indicates a null value).

Passing callbacks

You can pass callbacks as arguments to dart_eval using $Closure:

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

void main() {
  final program = '''
    void main(Function callback) {
      callback('Hello');
    }
  ''';

  eval(program, function: 'main', args: [
    $Closure((runtime, target, args) {
      print(args[0]!.$value + '!');
      return null;
    })
  ]); // -> prints 'Hello!'
}

Advanced usage

For more advanced usage, you can use the Compiler and Runtime classes directly, which will allow you to use multiple 'files' and customize how the program is run:

import 'package:dart_eval/dart_eval.dart';

void main() {
  final compiler = Compiler();
  
  final program = compiler.compile({'my_package': {
    'main.dart': '''
      import 'package:my_package/finder.dart';
      void main() {
        final parentheses = findParentheses('Hello (world)');
        if (parentheses.isNotEmpty) print(parentheses); 
      }
    ''',
    'finder.dart': r'''
      List<int> findParentheses(string) {
        final regex = RegExp(r'\((.*?)\)');
        final matches = regex.allMatches(string);
        return matches.map((match) => match.start).toList();
      }
    '''
  }});
  
  final runtime = Runtime.ofProgram(program);
  print(runtime.executeLib(
    'package:my_package/main.dart', 'main')); // prints '[6]'
}

Entrypoints and tree-shaking

dart_eval uses tree-shaking to avoid compiling unused code. By default, any file named main.dart or that contains runtime overrides will be treated as an entrypoint and guaranteed to be compiled in its entirety. To add additional entrypoints, append URIs to the Compiler.entrypoints array:

final compiler = Compiler();
compiler.entrypoints.add('package:my_package/some_file.dart');
compiler.compile(...);

Compiling to a file

If possible, it's recommended to pre-compile your Dart code to EVC bytecode, to avoid runtime compilation overhead. (This is still runtime code execution, it's just executing a more efficient code format.) Multiple files will be compiled to a single bytecode block.

import 'dart:io';
import 'package:dart_eval/dart_eval.dart';

void main() {
  final compiler = Compiler();
  
  final program = compiler.compile({'my_package': {
    'main.dart': '''
      int main() {
        var count = 0;
        for (var i = 0; i < 1000; i++) {
          count += i;
        }
        return count;
      }
    '''
  }});
  
  final bytecode = program.write();
  
  final file = File('program.evc');
  file.writeAsBytesSync(bytecode);
}

You can then load and execute the program later:

import 'dart:io';
import 'package:dart_eval/dart_eval.dart';

void main() {
  final file = File('program.evc');
  final bytecode = file
      .readAsBytesSync()
      .buffer
      .asByteData();
  
  final runtime = Runtime(bytecode);
  print(runtime.executeLib(
    'package:my_package/main.dart', 'main')); // prints '499500'
}

Using the CLI

The dart_eval CLI allows you to compile existing Dart projects to EVC bytecode, as well as run and inspect EVC bytecode files.

To enable the CLI globally, run:

dart pub global activate dart_eval

Compiling a project

The CLI supports compiling standard Dart projects. To compile a project, run:

cd my_project
dart_eval compile -o program.evc

This will generate an EVC file in the current directory called program.evc. dart_eval will attempt to compile Pub packages, but it's recommended to avoid them as they may use features that dart_eval doesn't support yet.

The compiler also supports compiling with JSON-encoded bridge bindings. To add these, create a folder in your project root called .dart_eval, add a bindings subfolder, and place JSON binding files there. The compiler will automatically load these bindings and make them available to your project.

Running a program

To run the generated EVC file, use:

dart_eval run program.evc -p package:my_package/main.dart -f main

Note that the run command does not support bindings, so any file compiled with bindings will need to be run in a specialized runner that includes the necessary runtime bindings.

Inspecting an EVC file

You can dump the op codes of an EVC file using:

dart_eval dump program.evc

Return values

Like with arguments, dart_eval will return a $Value wrapper for most values except ints, doubles, bools, and Lists. If you don't like this inconsistency, specifying a function's return value as dynamic will force dart_eval to always box the return value in a $Value wrapper.

Note that this does not apply to the eval() method, which automatically unboxes all return values for convenience.

Security and permissions

dart_eval is designed to be secure. The dart_eval runtime functions like a virtual machine, effectively sandboxing the code it executes. By default, the runtime will not allow running programs to access the file system, network, or other system resources, but these permissions can be enabled on a granular basis using runtime.grant:

final runtime = Runtime(bytecode);

// Allow full access to the file system
runtime.grant(FilesystemPermission.any);

// Allow access to a specific network domain
runtime.grant(NetworkPermission.url('example.com'));

// Allow access to a specific network resource
runtime.grant(NetworkPermission.url('https://dart.dev/api/users.json'));

// Using the eval() method
eval(source, permissions: [
  NetworkPermission.any,
  FilesystemReadPermission.directory('/home/user/mydata'), 
]);

Permissions can also be revoked using runtime.revoke.

When writing bindings that access sensitive resources, you can check whether a permission is enabled using runtime.checkPermission, or assert using runtime.assertPermission. Out of the box, dart_eval includes the FilesystemPermission and NetworkPermission classes ('filesystem' and 'network' domains, respectively) as well as read/write only variations of FilesystemPermission, but you can also create your own custom permissions by implementing the Permission interface.

Interop

Interop is a general term for methods in which we can access, use, and modify data from dart_eval in Dart. Enabling this access is a high priority for dart_eval.

There are three main levels of interop:

  • Value interop
  • Wrapper interop
  • Bridge interop

Value interop

Value interop happens automatically whenever dart_eval is working with an object backed by a real Dart value. (Therefore, an int and a string are value interop enabled, but a class created inside Eval isn't.) To access the backing object of a $Value, use its $value property. If the value is a collection like a Map or a List, you can use its $reified property to also unwrap the values it contains.

Wrapper interop

Using a wrapper enables the Eval environment to access the functions and fields on a class created outside Eval. It's much more powerful than value interop, and more performant than bridge interop, making it a great choice for certain use cases. To use wrapper interop, create a class that implements $Instance, and a compile-time class definition. Then, override $getProperty, $setProperty and $getRuntimeType to enable the Eval environment to access the class's properties and methods:

import 'package:dart_eval/dart_eval.dart';
import 'package:dart_eval/dart_eval_bridge.dart';
import 'package:dart_eval/dart_eval_extensions.dart';

/// An example class we want to wrap
class Book {
  Book(this.pages);
  final List<String> pages;
  
  String getPage(int index) => pages[index];
}

/// This is our wrapper class
class $Book implements $Instance {
  /// Create a type specification for the dart_eval compiler
  static final $type = BridgeTypeSpec('package:hello/book.dart', 'Book').ref;

  /// Create a class declaration for the dart_eval compiler
  static final $declaration = BridgeClassDef(BridgeClassType($type),
    constructors: {
      // Define the default constructor with an empty string
      '': BridgeFunctionDef(returns: $type.annotate, params: [
        'pages'.param(CoreTypes.string.ref.annotate)
      ]).asConstructor
    },
    methods: {
      'getPage': BridgeFunctionDef(
        returns: CoreTypes.string.ref.annotate,
        params: ['index'.param(CoreTypes.int.ref.annotate)],
      ).asMethod,
    }, wrap: true);

  /// Override $value and $reified to return the value
  @override
  final Book $value;

  @override
  get $reified => $value;
  
  /// Create a constructor that wraps the Book class
  $Book.wrap(this.$value);
  
  static $Value? $new(
    Runtime runtime, $Value? target, List<$Value?> args) {
    return $Book.wrap(Book(args[0]!.$value));
  }

  /// Create a wrapper for property and method getters
  @override
  $Value? $getProperty(Runtime runtime, String identifier) {
    if (identifier == 'getPage') {
      return $Function((_, target, args) {
        return $String($value.getPage(args[0]!.$value));
      });
    }
    return $Object(this).$getProperty(runtime, identifier);
  }

  /// Create a wrapper for property setters
  @override
  void $setProperty(Runtime runtime, String identifier, $Value value) {
    return $Object(this).$setProperty(runtime, identifier, value);
  }

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

/// Now we can use it in dart_eval!
void main() {
  final compiler = Compiler();
  compiler.defineBridgeClass($Book.$declaration);
  
  final program = compiler.compile({'hello' : { 
    'main.dart': '''
      import 'book.dart';
      void main() {
        final book = Book(['Hello world!', 'Hello again!']);
        print(book.getPage(1));
      }
    '''
  }});

  final runtime = Runtime.ofProgram(program);
  // Register static methods and constructors with the runtime
  runtime.registerBridgeFunc('package:hello/book.dart', 'Book.', $Book.$new);

  runtime.executeLib('package:hello/main.dart', 'main'); // -> 'Hello again!'
}

For more information, see the wrapper interop wiki page.

(Experimental) Binding generation for wrappers

As of v0.7.1 the dart_eval CLI includes an experimental wrapper binding generator. It can be invoked in a project using dart_eval bind, and will generate bindings for all classes annotated with the @Bind annotation from the eval_annotation package. You can also pass the '--all' flag to generate bindings for all classes in the project. Note that generated bindings don't support every edge case, and may require manual adjustment.

Binding generation cannot currently create JSON bindings directly, but you can use the generated Dart bindings to create JSON bindings using a BridgeSerializer.

Bridge interop

Bridge interop enables the most functionality: Not only can dart_eval access the fields of an object, but it can also be extended, allowing you to create subclasses within Eval and use them outside of dart_eval. For example, this can be used to create custom Flutter widgets that can be dynamically updated at runtime. Bridge interop is also in some ways simpler than creating a wrapper, but it comes at a performance cost, so should be avoided in performance-sensitive situations. To use bridge interop, extend the original class and mixin $Bridge:

// ** See previous example for the original class and imports **

/// This is our bridge class
class $Book$bridge extends Book with $Bridge<Book> {
  static final $type = ...; // See previous example
  static final $declaration = ...; // Previous example, but use bridge: true instead of wrap

  /// Recreate the original constructor
  $Book$bridge(super.pages);

  static $Value? $new(
    Runtime runtime, $Value? target, List<$Value?> args) {
    return $Book$bridge((args[0]!.$reified as List).cast());
  }

  @override
  $Value? $bridgeGet(String identifier) {
    if (identifier == 'getPage') {
      return $Function((_, target, args) {
        return $String(getPage(args[0]!.$value));
      });
    } 
    throw UnimplementedError('Unknown property $identifier');
  }

  @override
  $Value? $bridgeSet(String identifier) => 
    throw UnimplementedError('Unknown property $identifier');

  /// Override the original class' properties and methods
  @override
  String getPage(int index) => $_invoke('getPage', [$int(index)]);

  @override
  List<String> get pages => $_get('pages');
}

void main() {
  final compiler = Compiler();
  compiler.defineBridgeClass($Book$bridge.$declaration);
  
  final program = compiler.compile({'hello' : { 
    'main.dart': '''
      import 'book.dart';
      class MyBook extends Book {
        MyBook(List<String> pages) : super(pages);
        String getPage(int index) => 'Hello world!';
      }

      Book main() {
        final book = MyBook(['Hello world!', 'Hello again!']);
        return book;
      }
    '''
  }});

  final runtime = Runtime.ofProgram(program);
  runtime.registerBridgeFunc(
    'package:hello/book.dart', 'Book.', $Book$bridge.$new, bridge: true);

  // Now we can use the new book class outside dart_eval!
  final book = runtime.executeLib('package:hello/main.dart', 'main') 
    as Book;
  print(book.getPage(1)); // -> 'Hello world!'
}

An example featuring both bridge and wrapper interop is available in the example directory. For more information, see the wiki page on bridge classes.

Plugins

To configure interop for compilation and runtime, it's recommended to create an EvalPlugin which enables reuse of Compiler instances. Basic example:

class MyAppPlugin implements EvalPlugin {
  @override
  String get identifier => 'package:myapp';

  @override
  void configureForCompile(BridgeDeclarationRegistry registry) {
    registry.defineBridgeTopLevelFunction(BridgeFunctionDeclaration(
      'package:myapp/functions.dart',
      'loadData',
      BridgeFunctionDef(
          returns: BridgeTypeAnnotation(BridgeTypeRef(CoreTypes.object)), params: [])
    ));
    registry.defineBridgeClass($CoolWidget.$declaration);
  }

  @override
  void configureForRuntime(Runtime runtime) {
    runtime.registerBridgeFunc('package:myapp/functions.dart', 'loadData', 
        (runtime, target, args) => $Object(loadData()));
    runtime.registerBridgeFunc('package:myapp/classes.dart', 'CoolWidget.', $CoolWidget.$new);
  }
}

You can then use this plugin with Compiler.addPlugin and Runtime.addPlugin.

Runtime overrides

dart_eval includes a runtime overrides system that allows you to dynamically swap in new implementations of functions and constructors at runtime. To use it, add a null-coalescing call to the runtimeOverride() method at every spot you want to be able to swap:

void main() {
  // Give the override a unique ID
  final result = runtimeOverride('#myFunction') ?? myFunction();
  print(result);
}

String myFunction() => 'Original version of string';

Note that in some cases you may have to cast the return value of runtimeOverride as dart_eval is unable to specify generic parameters to the Dart type system.

Next, mark a function in the eval code with the @RuntimeOverride annotation:

@RuntimeOverride('#myFunction')
String myFunction() => 'Updated version of string'

Finally, follow the normal instructions to compile and run the program, but call loadGlobalOverrides on the Runtime. This will set the runtime as the single global runtime for the program, and load its overrides to be accessible by hot wrappers.

When the program is run, the runtime will automatically replace the function call with the new implementation.

Overrides can also be versioned, allowing you to roll out updates to a function immediately using dart_eval and revert to a new native implementation after an official update is released. To version an override, simply add a semver version constraint to the @RuntimeOverride annotation:

@RuntimeOverride('#login_page_get_data', version: '<1.4.0')

When running the program, specify its current version by setting the value of the runtimeOverrideVersion global property:

runtimeOverrideVersion = Version.parse('1.3.0');

Now, when the program is run, the runtime will automatically replace the instantiation only if the app version is less than 1.4.0.

Contributing

See Contributing.

FAQ

How does it work?

dart_eval is a fully Dart-based implementation of a bytecode compiler and runtime. First, the Dart analyzer is used to parse the code into an AST (abstract syntax tree). Then, the compiler looks at each of the declarations in turn, and recursively compiles to a linear bytecode format.

For evaluation dart_eval uses Dart's optimized dynamic dispatch. This means each bytecode is actually a class implementing EvcOp and we call its run() method to execute it. Bytecodes can do things like push and pop values on the stack, add numbers, and jump to other places in the program, as well as more complex Dart-specific operations like create a class.

See the in-depth overview wiki page for more information.

Does it support Flutter?

Yes! Check out flutter_eval.

How fast is it?

Preliminary testing shows that dart_eval running in AOT-compiled Dart is 10-50x slower than standard AOT Dart and is approximately on par with a language like Ruby. It's important to remember this only applies to code running directly in the dart_eval VM, and not any code it interacts with. For example, most Flutter apps spend the vast majority of their performance budget in the Flutter framework itself, so the speed impact of dart_eval is usually negligible.

Is this allowed in the App Store?

Though Apple's official guidelines are unclear, many popular apps use similar techniques to dynamically update their code. For example, apps built on React Native often use its custom Hermes JavaScript engine to enable dynamic code updates. Note that Apple is likely to remove apps if they introduce policy violations in updates, regardless of the technology used.

Language feature support table

The following table details the language features supported by dart_eval with native Dart code. Feature support may vary when bridging.

Feature Support level Tests
Imports [1], [2], [3]
Exports [1], [2]
part / part of [1]
show and hide [1]
Conditional imports N/A
Deferred imports N/A
Functions [1]
Anonymous functions [1], [2], [3], [4], [5], [6]
Arrow functions [1], [2]
Sync generators N/A
Async generators N/A
Tear-offs [1], [2], [3]
For loops [1], [2]
While loops [1]
Do-while loops [1]
For-each loops [1], [2]
Async for-each N/A
Switch statements N/A
Labels, break & continue Partial [1], [2]
If statements [1]
Try-catch [1], [2], [3], [4], [5]
Try-catch-finally [1], [2], [3], [4], [5]
Lists [1]
Iterable [1], [2]
Maps Partial [1], [2], [3], [4]
Sets N/A
Collection for [1], [2]
Collection if [1], [2]
Spreads N/A
Classes [1]
Class static methods [1], [2]
Getters and setters [1]
Factory constructors [1]
Redirecting constructors [1]
new keyword [1]
Class inheritance [1]
Abstract and implements Partial [1]
this keyword [1], [2]
super keyword [1]
Super constructor params [1]
Mixins N/A
Futures Partial [1], [2]
Async/await [1], [2], [3]
Streams Partial [1]
String interpolation [1]
Enums Partial [1], [2], [3]
Generic function types Partial [1]
Typedefs N/A
Generic classes Partial
Type tests (is) [1], [2]
Casting (as) [1], [2], [3]
assert [1]
Null safety Partial
Late initialization N/A
Cascades [1], [2]
Ternary expressions [1]
Null coalescing expressions [1], [2]
Extension methods N/A
Const expressions Partial N/A
Isolates N/A
Record types N/A
Patterns N/A

Features and bugs

Please file feature requests and bugs at the issue tracker. If you need help, use the discussion board.

dart_eval's People

Contributors

canewsin avatar delemike avatar ethanblake4 avatar g123k avatar goflutterjava avatar kodjodevf avatar maks avatar maxiee avatar noobware1 avatar oneplus1000 avatar shoothzj avatar wrbl606 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dart_eval's Issues

Potential optimization: Auto AOT when possible, and fallback to interpreted

For example, say I release v1.0.0 app with 10 pages. Then in v1.0.1, I change the UI (i.e. HomePage.build), and a few logic (e.g. HomePage.handleMyButtonClick).

Then, would be great to have all other pages (SecondPage, ThirdPage, ...) all in AOT, and also making all other untouched methods in HomePage still AOT, while only making HomePage.build and HomePage.handleMyButtonClick interpreted.

This is because an app will not change most of things in a hot update.

(Not sure whether I have mentioned this thought somewhere before)

Looking to Collaborate

Hi @ethanblake4 ! I think we exchanged some posts over Reddit about a year ago (and what a long year it's been).

I'm a huge fan of your work! I think it's amazing that you're building a DBC interpreter in Dart!

I'm building Hydro-SDK. Hydro-SDK aims to allow developers to author Flutter content and to publish and update that content over the air (outside the app stores). I gave a talk about Hydro-SDK last year at DartUP (where I also mentioned dart_eval 🙂 ).

Hydro-SDK accomplishes this by providing an embedded Lua interpreter written in Dart and a toolchain to compile Typescript to Lua, compile, package and distribute code. The Lua VM supports hot reload with some limitations. There's some limitations to the Typescript support as well.

I've been focused the last year on building out tools for automatic binding generation to allow using arbitrary Dart packages with Hydro-SDK.

Everything about Hydro-SDK aims to be as language agnostic as possible. I would love to collaborate on embedding dart_eval into Hydro-SDK later this year if you're interested. At the very least, I'd love to compare notes with you on binding systems and VMs. 🙂

How to import Library in dart_eval

For example, is it possiable to implement package:http with this lib like

import 'package:http/http.dart';
var parser = Parse();
var scope = parser.parse('''
    class Test extends T{
      String uri = 'https://www.google.com';
      late Map<String, String> headers;
      test() {
        headers = {"referer": uri};
      }
      @override
      getResponse() async {
        return await get(Uri.parse(url), headers: headers);
      }
    }
    Test get() {
      return Test();
    }
 ''');
var test= scope('get', []) as T;
var response = test.response();

What's left before having a complete hot-update solution

IMHO (hopefully I am not too optimistic about this!) with the open source library of you and me, maybe we can create a full hot-update solution and solve the flutter/flutter#14330.

I guess maybe we need to finish these components:

  • dart JIT mode - use in Android (since faster)
  • dart_eval and flutter_eval - use in ios, because it needs interpreted code
  • flutter_smooth - use to speed up JIT and interpreted code
  • glue and scaffolding - feed app code into JIT/dart_eval, etc

Anyway this issue is not a real "issue" (problem), but just a little reminder about what I think :)

Error using example from Readme

Hi,
the following code is taken from README.md.
It will result in list of Errors using ^0.4.1.

  test('dart_eval', () {
    final program = '''
      class Cat {
        Cat(this.name);
        final String name;
        String speak() {
          return name;
        }
      }
      String main() {
        final cat = Cat('Fluffy');
        return cat.speak();
      }
  ''';

    print(eval(program, function: 'main')); // -> 'F
  });
: Error: The getter 'name2' isn't defined for the class 'NamedCompilationUnitMember'.
../…/compiler/compiler.dart:403
- 'NamedCompilationUnitMember' is from 'package:analyzer/dart/ast/ast.dart' ('../../../../flutter_sdk/.pub-cache/hosted/pub.dartlang.org/analyzer-4.2.0/lib/dart/ast/ast.dart').
package:analyzer/…/ast/ast.dart:1
Try correcting the name to the name of an existing getter, or defining a getter or field named 'name2'.
      final name = declaration.name2.value() as String;
                               ^^^^^
: Error: The getter 'name2' isn't defined for the class 'MethodDeclaration'.
../…/compiler/compiler.dart:416
- 'MethodDeclaration' is from 'package:analyzer/dart/ast/ast.dart' ('../../../../flutter_sdk/.pub-cache/hosted/pub.dartlang.org/analyzer-4.2.0/lib/dart/ast/ast.dart').
package:analyzer/…/ast/ast.dart:1
Try correcting the name to the name of an existing getter, or defining a getter or field named 'name2'.
            final mName = member.name2.value() as String;

                                 ^^^^^
: Error: The getter 'name2' isn't defined for the class 'ClassDeclaration'.
../…/compiler/compiler.dart:432
- 'ClassDeclaration' is from 'package:analyzer/dart/ast/ast.dart' ('../../../../flutter_sdk/.pub-cache/hosted/pub.dartlang.org/analyzer-4.2.0/lib/dart/ast/ast.dart').
package:analyzer/…/ast/ast.dart:1
Try correcting the name to the name of an existing getter, or defining a getter or field named 'name2'.
                final name = (declaration.name2.value() as String) + '.' + (field.name2.value() as String);

Maybe post and discuss on with community?

If this is to be the solution of flutter hot update, IMHO discussing with more smart people may be helpful. For example, there may be serious problems that we both have not realized which completely blocks the package - good to know it earlier than later.

In addition, since Hot Update is the most upvoted issue in Flutter repo, I guess there will be some more people interested in this package and have the open-source spirits. Then, this package may have some PR and develop faster, i.e. gain hot-update sooner.

Possible locations that I come up with (probably also other places that I do no know):

  • reddit
  • dart github repo / flutter dart discord channel - maybe dart team will provide some insights

An example for implementing Flutter with this lib

Hi @ethanblake4, this is truly a great lib & exactly what I was looking for. You have mentioned that we can use Flutter widgets with this lib. I tried to add them using the example you have mentioned. But from my approach, it seems I have to add boilerplate code for all the required Widgets ie for text, container, etc. Is there a better way to approach this? Could you share an example, if possible? Thanks.

Runtime overrides/hot wrappers

  • Generated EVC should associate string IDs with bytecode offsets.
  • Some way (annotation?) to define those IDs in the source
  • Determine what if any stdlib classes should be hot wrappers

Errors in current main

First off thank you very much for creating this awesome package!

I cloned current main (18999d0) and tried to run the example but I get these errors:

dart dart_eval_example.dart 
../lib/src/eval/compiler/context.dart:2:8: Error: Error when reading '../lib/source_node_wrapper.dart': No such file or directory
import 'package:dart_eval/source_node_wrapper.dart';
       ^
../lib/src/eval/compiler/context.dart:203:28: Error: The method 'BuiltinValue' isn't defined for the class 'CompilerContext'.
 - 'CompilerContext' is from 'package:dart_eval/src/eval/compiler/context.dart' ('../lib/src/eval/compiler/context.dart').
Try correcting the name to the name of an existing method, or defining a method named 'BuiltinValue'.
            final _index = BuiltinValue(intval: frameRef[i + 1].scopeFrameOffset).push(this);
                           ^^^^^^^^^^^^
../lib/src/eval/compiler/context.dart:209:26: Error: The method 'BuiltinValue' isn't defined for the class 'CompilerContext'.
 - 'CompilerContext' is from 'package:dart_eval/src/eval/compiler/context.dart' ('../lib/src/eval/compiler/context.dart').
Try correcting the name to the name of an existing method, or defining a method named 'BuiltinValue'.
          final _index = BuiltinValue(intval: v.scopeFrameOffset).push(this);

It seems like source_node_wrapper.dart hadn't been commited? I saw there was a dev dep on builder_runner so I ran it, but that didn't generate any files.

failing tests

With the current main branch (06dda6a) I get the following tests failing.
It looks like they are all around the result having a extra type prefix, eg.

 Expected: <8>
    Actual: $int:<8>

Should they all be updated for the new dollar-sign prefix?

Also would it be worth setting up a Github actions CI build for this repo to catch these in the future?

I'm happy to contribute a PR to fix both these if you would like?

uilt test:test.
00:04 +12 -1: test/dart_eval_test.dart: Class tests Field formal parameters, external field access [E]                            
  Null check operator used on a null value
  package:dart_eval/src/eval/compiler/expression/identifier.dart 49:67           resolveInstanceDeclaration
  package:dart_eval/src/eval/compiler/reference.dart 134:35                      IdentifierReference.getValue
  package:dart_eval/src/eval/compiler/expression/identifier.dart 13:48           compileIdentifier
  package:dart_eval/src/eval/compiler/expression/method_invocation.dart 31:20    compileMethodInvocation
  package:dart_eval/src/eval/compiler/expression/expression.dart 27:12           compileExpression
  package:dart_eval/src/eval/compiler/statement/variable_declaration.dart 25:19  compileVariableDeclarationList
  package:dart_eval/src/eval/compiler/statement/variable_declaration.dart 12:3   compileVariableDeclarationStatement
  package:dart_eval/src/eval/compiler/statement/statement.dart 19:12             compileStatement
  package:dart_eval/src/eval/compiler/statement/block.dart 16:20                 compileBlock
  package:dart_eval/src/eval/compiler/declaration/function.dart 49:14            compileFunctionDeclaration
  package:dart_eval/src/eval/compiler/declaration/declaration.dart 18:5          compileDeclaration
  package:dart_eval/src/eval/compiler/compiler.dart 248:9                        Compiler.compileSources.<fn>.<fn>
  dart:collection                                                                _LinkedHashMapMixin.forEach
  package:dart_eval/src/eval/compiler/compiler.dart 237:13                       Compiler.compileSources.<fn>
  dart:collection                                                                _LinkedHashMapMixin.forEach
  package:dart_eval/src/eval/compiler/compiler.dart 234:30                       Compiler.compileSources
  package:dart_eval/src/eval/compiler/compiler.dart 80:12                        Compiler.compile
  package:dart_eval/src/eval/compiler/compiler.dart 288:21                       Compiler.compileWriteAndLoad
  test/dart_eval_test.dart 276:24                                                main.<fn>.<fn>
  
00:04 +12 -2: test/dart_eval_test.dart: Class tests "this" keyword [E]                                                            
  Expected: <8>
    Actual: $int:<8>
  
  package:test_api                expect
  test/dart_eval_test.dart 327:7  main.<fn>.<fn>
  
00:04 +12 -3: test/dart_eval_test.dart: Class tests Implicit and "this" field access from closure [E]                             
  Expected: <12>
    Actual: $int:<12>
  
  package:test_api                expect
  test/dart_eval_test.dart 358:7  main.<fn>.<fn>
  
00:04 +12 -4: test/dart_eval_test.dart: Class tests Simple static method [E]                                                      
  Expected: <10>
    Actual: $int:<10>
  
  package:test_api                expect
  test/dart_eval_test.dart 378:7  main.<fn>.<fn>
  
00:04 +12 -5: test/dart_eval_test.dart: Class tests Implicit static method scoping [E]                                            
  Expected: <3>
    Actual: $int:<3>
  
  package:test_api                expect
  test/dart_eval_test.dart 406:7  main.<fn>.<fn>
  
00:04 +20 -6: test/dart_eval_test.dart: File and library composition Import hiding [E]                                            
  Null check operator used on a null value
  package:dart_eval/src/eval/compiler/expression/identifier.dart 49:67           resolveInstanceDeclaration
  package:dart_eval/src/eval/compiler/reference.dart 134:35                      IdentifierReference.getValue
  package:dart_eval/src/eval/compiler/expression/identifier.dart 13:48           compileIdentifier
  package:dart_eval/src/eval/compiler/expression/method_invocation.dart 31:20    compileMethodInvocation
  package:dart_eval/src/eval/compiler/expression/expression.dart 27:12           compileExpression
  package:dart_eval/src/eval/compiler/statement/variable_declaration.dart 25:19  compileVariableDeclarationList
  package:dart_eval/src/eval/compiler/statement/variable_declaration.dart 12:3   compileVariableDeclarationStatement
  package:dart_eval/src/eval/compiler/statement/statement.dart 19:12             compileStatement
  package:dart_eval/src/eval/compiler/statement/block.dart 16:20                 compileBlock
  package:dart_eval/src/eval/compiler/declaration/function.dart 49:14            compileFunctionDeclaration
  package:dart_eval/src/eval/compiler/declaration/declaration.dart 18:5          compileDeclaration
  package:dart_eval/src/eval/compiler/compiler.dart 248:9                        Compiler.compileSources.<fn>.<fn>
  dart:collection                                                                _LinkedHashMapMixin.forEach
  package:dart_eval/src/eval/compiler/compiler.dart 237:13                       Compiler.compileSources.<fn>
  dart:collection                                                                _LinkedHashMapMixin.forEach
  package:dart_eval/src/eval/compiler/compiler.dart 234:30                       Compiler.compileSources
  package:dart_eval/src/eval/compiler/compiler.dart 80:12                        Compiler.compile
  test/dart_eval_test.dart 639:32                                                main.<fn>.<fn>
  
00:04 +20 -7: test/dart_eval_test.dart: File and library composition Export chains [E]                                            
  Null check operator used on a null value
  package:dart_eval/src/eval/compiler/expression/identifier.dart 49:67           resolveInstanceDeclaration
  package:dart_eval/src/eval/compiler/reference.dart 134:35                      IdentifierReference.getValue
  package:dart_eval/src/eval/compiler/expression/identifier.dart 13:48           compileIdentifier
  package:dart_eval/src/eval/compiler/expression/method_invocation.dart 31:20    compileMethodInvocation
  package:dart_eval/src/eval/compiler/expression/expression.dart 27:12           compileExpression
  package:dart_eval/src/eval/compiler/statement/variable_declaration.dart 25:19  compileVariableDeclarationList
  package:dart_eval/src/eval/compiler/statement/variable_declaration.dart 12:3   compileVariableDeclarationStatement
  package:dart_eval/src/eval/compiler/statement/statement.dart 19:12             compileStatement
  package:dart_eval/src/eval/compiler/statement/block.dart 16:20                 compileBlock
  package:dart_eval/src/eval/compiler/declaration/function.dart 49:14            compileFunctionDeclaration
  package:dart_eval/src/eval/compiler/declaration/declaration.dart 18:5          compileDeclaration
  package:dart_eval/src/eval/compiler/compiler.dart 248:9                        Compiler.compileSources.<fn>.<fn>
  dart:collection                                                                _LinkedHashMapMixin.forEach
  package:dart_eval/src/eval/compiler/compiler.dart 237:13                       Compiler.compileSources.<fn>
  dart:collection                                                                _LinkedHashMapMixin.forEach
  package:dart_eval/src/eval/compiler/compiler.dart 234:30                       Compiler.compileSources
  package:dart_eval/src/eval/compiler/compiler.dart 80:12                        Compiler.compile
  test/dart_eval_test.dart 670:32                                                main.<fn>.<fn>
  
00:04 +23 -7: Some tests failed.     

Cannot open file, path = 'out.dbc' (OS Error: Read-only file system, errno = 30)

════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by gesture ═══════════════════════════════════════════
Cannot open file, path = 'out.dbc' (OS Error: Read-only file system, errno = 30)
════════════════════════════════════════════════════════════════════════════════

Add support for while loops

This will need:
Support in the parser (parser.dart)
A DartWhileStatement class that executes the while loop (statements.dart)

See the for loop class for reference

Super constructor params

dart_eval should support the new super property syntax sugar, eg:

class X extends Y {
  X(super.prop);
}

Tree-shaking

As of 8d3ed66 the compiler uses graphs to compose imports, which should make (basic, import-based) tree-shaking fairly straightforward.

However, given that you currently can use any function as an entrypoint it's unclear how best to implement this. Probably isn't relevant until #21 is finished.

Add mixins

Multiple interfaces for bridge still need to be figured out.

Support try/catch

Reproduction:

void _print() {
  try {
    print('a');
  } catch (e) {
    print('b');
  }
}

Seriousness/goal of the package?

So, do you plan to make it a prototype or experiment or toy, or make it the ultimate solution to Flutter hot update?

If the former, I completely understand since nobody has infinite free time. If the latter, I would like to spend some more time looking at it, trying to see e.g. performance/architecture/whatever improvements.

Flutter bindings

Bindings for Flutter are highly requested. Long term, this will come as a result of #20 but in the short term it would be nice to have some basic support for common Flutter widgets.

dart_eval runtime exception: type '$String' is not a subtype of type 'List<dynamic>' in type cast

I tried to run the following example code. But it failed with the following errors.

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

void main() {
  final compiler = Compiler();
  final program = compiler.compile({
    'my_package': {
      'main.dart': '''

      String hello(String params) {
        return "hello, \$params";
      }


      void main(List<String> args) {
        print(hello(args[0]));
      }
    '''
    }
  });

  final runtime = Runtime.ofProgram(program);
  runtime.setup();
  runtime.executeLib(
      'package:my_package/main.dart', 'main', [$String('nice to meet you')]);
}
>dart example1.dart 
Unhandled exception:
dart_eval runtime exception: type '$String' is not a subtype of type 'List<dynamic>' in type cast
#0      IndexList.run (package:dart_eval/src/eval/runtime/ops/primitives.dart:346:70)
#1      Runtime.execute (package:dart_eval/src/eval/runtime/runtime.dart:457:12)
#2      Runtime.executeNamed (package:dart_eval/src/eval/runtime/runtime.dart:448:12)

RUNTIME STATE
=============
Program offset: 15
Stack sample: [Instance of '$String', 0, null, null, null, null, null, null, null, null]
Call stack: [0, -1]
TRACE:
9: InvokeDynamic (L2.+)
10: PushReturnValue ()
11: Return (L4)
12: Pop (1)
13: PushScope (F2:86, 'main()')
14: PushConstantInt (0)
15: IndexList (L0[L1])  <<< EXCEPTION
16: PushArg (L2)
17: Call (@0)
18: PushReturnValue ()

#0      Runtime.execute (package:dart_eval/src/eval/runtime/runtime.dart:464:7)
#1      Runtime.executeNamed (package:dart_eval/src/eval/runtime/runtime.dart:448:12)
#2      Runtime.executeLib (package:dart_eval/src/eval/runtime/runtime.dart:443:12)
#3      main (file:///D:/code/flutter/dart_eval/example/example1.dart:25:11)
#4      _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#5      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)

>

And I also tried to change the last line of code to runtime.executeLib('package:my_package/main.dart', 'main', ['nice to meet you']); then it produced a similar error dart_eval runtime exception: type 'String' is not a subtype of type 'List<dynamic>' in type cast.

CLI: compile external packages

The dart_eval CLI should compile app dependencies from Pub, etc. Data about packages created by "pub get" is in .dart_tool/package_config.json.

Unknown field '&&'

I have this expression : "if (x>=50 && x<65) {return '-1';}" and when I want to scope '&&' isn't recognize.

The stacktrace :
image

Thank you for your reply.

Binding generation

dart_eval should have a tool to scan an existing package (including the Dart and Flutter SDK) and auto-generate bindings. Maybe can use package:build?

(for anyone who finds this: If you need to write bindings right now I suggest using GitHub Copilot, it does a fairly good job generating the boilerplate)

Comparison between this and JavaScript or Wasm based solutions?

(I am not against you! Just because if we are going to make it the solution, we have to think about all possible opposite voices by ourselves.)

So, what if someone (or us?) implements hot update via:

  1. JavaScript?
  2. Wasm?

(Hixie also mentioned these in 14330)

My non-mature answer:

  • JS/Wasm have a much faster runtime (compared with dart_eval)
  • But the bridge may be slow (or slower? or faster?)
  • If bridge takes most of the time (we must have data to support this), then they do not have overall advantage

P.S. For some existing hot update solutions, here is a comparison article: https://juejin.cn/post/7033708048321347615 (Not in English, may need a translator...)

Null check operator used on a null value

original class

class OpenApiAsyncDebug {
  final name = "open async debug";

  Future<String> helloWorld() async {
    return "helloWorld";
  }

  Future<String> echo(String str) async {
    return str;
  }
}

bridge class

import 'package:dart_eval/dart_eval_bridge.dart';

class OpenApiAsyncBridge {
  static const $type = BridgeTypeReference.unresolved(
      BridgeUnresolvedTypeReference(
          'package:paas_dashboard_flutter/open/open_api_debug_async.dart', 'OpenApiAsyncDebug'),
      []);

  static const $declaration = BridgeClassDeclaration($type, isAbstract: false, constructors: {}, methods: {
    'helloWorld': BridgeMethodDeclaration(
        false,
        BridgeFunctionDescriptor(
            BridgeTypeAnnotation(
                BridgeTypeReference.unresolved(BridgeUnresolvedTypeReference('dart:core', 'Future'), []), false),
            {},
            [],
            {})),
  }, getters: {}, setters: {}, fields: {});
}

eval code

static void evalPiece(String piece) {
    var compiler = Compiler();
    compiler.defineBridgeClasses([OpenApiAsyncBridge.$declaration]);
    final code = "void main() async {\n"
        + "final openApiSyncDebug = OpenApiSyncDebug();\n"
        + "final openApiAsyncDebug = OpenApiAsyncDebug();\n"
        + piece + "\n"
        + "}";
    final program = compiler.compile({
      'package:example': {
        'main.dart': code
      }
    });
    final runtime = Runtime.ofProgram(program);
    runtime.execute(0);
  }

Error Message

Failed to load "/Users/xxxxxx/code/flutter/paas_dashboard_flutter/test/eval/eval_service_test_debug_async_helloworld.dart": Null check operator used on a null value

Add support for Sets

This will need:
Support in the parser (parser.dart)
A new EvalSet type in primitives.dart
A new DartSetLiteral class in literals.dart
???

Making a Dart interpreter inside official dart repo?

Hi, this project looks great and looks like a big amount of work! Excited to see it is still growing, and looking forward to seeing it in production :)

If I understand correctly, this looks like a Dart interpreter. Is it possible that we directly contribute to the dartlang repo and create an interpreter there? They use C++ so I guess the interpretation may run faster since we can control more low-level things.

Given that Dart already have JIT and AOT, adding an interpreted mode looks like something that will be accepted there.

Bytedance maintain a fork of Dart that does interpretation, for a similar reason; one could use that (I think it's public?). (by hixie in flutter/flutter#14330 (comment)

So technically speaking, it should also be implementable.

I really want to see hot-update become reality. That's why I am suggesting this. Because I am quite afraid many people will not choose an interpreter that is not in the dart repository.

Another advantage is that, we can utilize all infra of dart sdk internals. There must be a lot of optimizations etc already there that we may even be able to reuse :)

Support String.substring()

I'm trying to figure out what is supported for core types. Eg I have a simple test harness:

import 'package:dart_eval/dart_eval.dart';

void main(List<String> arguments) {
  final program = '''
      int main() {
          // my test code goes here
      }
  ''';

  print("eval result:${eval(program)}");
}

If my program text is:

bool main() {
        final cat = 'Fluffy';
        return cat.isEmpty;
      }

I get the expected result: eval result:false

For:

String main() {
        final cat = 'Fluffy';
        return cat.substring(0,2);
      }

However I get:

Unhandled exception:
Null check operator used on a null value
#0      resolveInstanceMethod (package:dart_eval/src/eval/compiler/expression/method_invocation.dart:201:62)
#1      new AlwaysReturnType.fromInstanceMethod (package:dart_eval/src/eval/compiler/type.dart:438:16)
#2      AlwaysReturnType.fromInstanceMethodOrBuiltin (package:dart_eval/src/eval/compiler/type.dart:471:28)
#3      Variable.invoke (package:dart_eval/src/eval/compiler/variable.dart:210:28)
#4      _invokeWithTarget (package:dart_eval/src/eval/compiler/expression/method_invocation.dart:135:14)
#5      compileMethodInvocation (package:dart_eval/src/eval/compiler/expression/method_invocation.dart:29:12)
#6      compileExpression (package:dart_eval/src/eval/compiler/expression/expression.dart:27:12)
#7      compileReturn (package:dart_eval/src/eval/compiler/statement/return.dart:38:17)
#8      compileStatement (package:dart_eval/src/eval/compiler/statement/statement.dart:24:12)
#9      compileBlock (package:dart_eval/src/eval/compiler/statement/block.dart:16:20)
#10     compileFunctionDeclaration (package:dart_eval/src/eval/compiler/declaration/function.dart:49:14)
#11     compileDeclaration (package:dart_eval/src/eval/compiler/declaration/declaration.dart:18:5)
#12     Compiler.compileSources.<anonymous closure>.<anonymous closure> (package:dart_eval/src/eval/compiler/compiler.dart:248:9)
#13     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:614:13)
#14     Compiler.compileSources.<anonymous closure> (package:dart_eval/src/eval/compiler/compiler.dart:237:13)
#15     _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:614:13)
#16     Compiler.compileSources (package:dart_eval/src/eval/compiler/compiler.dart:234:30)
#17     Compiler.compile (package:dart_eval/src/eval/compiler/compiler.dart:80:12)
#18     eval (package:dart_eval/src/eval/eval.dart:29:28)
#19     main (file:///home/maks/work/dart_eval/example/minimal_example.dart:11:24)
#20     _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:295:32)
#21     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)

Is this because only Getters/Setters are curently supported?
The reason I'm trying this is to see if its possible now or in future for dart_eval to do "transparent" interop with "real dart" code.
I apologise if this is a silly question, as Ihaven't really been able to figure out yet how dart_eval calls into "real dart".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.