Press ESC to close

Flutter App : REPL (interactive shell) for Dart, supporting 3rd party packages, hot reload, and full grammar

dart_interactive

Flutter Package
CI

A lot of sibling languages have a REPL, and is quite helpful in everyday usage, while Dart did not have it (even though it was the 8th highest-voted request). So here it comes!

? Features

A full-featured REPL (interactive shell), with:

  • Use any third-party package freely
  • Auto hot-reload code anywhere, with state preserved
  • Supports full grammar in REPL
  • Play with existing code side-by-side

? Demo

Demo 1: Demonstrate features

  1. Use 3rd party package
>>> !dart pub add path // normal shell command >>> import 'package:path/path.dart'; // normal import >>> join('directory', 'file.txt') // use it (`join` is a function in 3rd party package `path`) directory/file.txt
  1. Auto hot-reload
>>> import 'a.dart'; >>> myFunc() hello, tom // ... change content of `a.dart` ... >>> myFunc() hello, alex
  1. Support full grammar
>>> a = 10; // support rich grammar >>> int g() => a++; class A {} class B {} ... class C extends A implements B { ... int b = 20; ... int f() { int c = 30; a++; b++; c++; return a+b+c+g(); } ... } >>> c = C() >>> c.f() 74 // support redefine class/method/... >>> class C extends A implements B { int b = 20; int f() => b; } >>> c.f() 21

Demo 2: Sample workflow

Surely, you do not have to use it like this. It is just a workflow that I personally feel comfortable when working with IPython/Juypter.

Suppose we have my_app.dart with some code, probably edited inside an IDE:

class Counter { int count = 0; String greet() => 'Hi Tom, you have count $count!'; }

Play with it a bit:

--directory path/to/my/package >>> import 'my_app.dart'; >>> counter = Counter(); >>> counter.count = 10; >>> counter.greet() Hi Tom, you have count 10! >>> counter.count = 20; >>> counter.greet() Hi Tom, you have count 20!

Then we realize something wrong and want to change it:

(change "Tom" to "Alex" inside `my_app.dart`)

Continue playing with it (auto hot reloaded, and state preserved):

>>> counter.greet() Hi Alex, you have count 20!

We can also use all dependencies in the package as well, since the REPL code is just like a normal code file in this package.

>>> import 'package:whatever_package'; >>> functionInWhateverPackage();

? Getting started

Install (just standard procedure of installing global dart packages):

dart pub global activate interactive

Use (just a normal binary):

interactive

And play with it ?

Detailed functionality list

Expressions

>>> a = 'Hello'; b = ' world!'; >>> '$a, $b' Hello, world!

Statements

>>> print(a) Hello

(All methods, not only print)

Functions

Define and redefine

>>> String f() => 'old'; >>> f() old >>> String f() => 'new'; >>> f() new

Use local and global variables

>>> a = 10; >>> int f() { int b = 20; a++; b++; return a+b; } >>> f() 32 >>> f() 33

Classes

Define and redefine, preserving states

>>> class C { int a = 10; int f() => a * 2; } >>> c = C(); print(c.f()); 20 >>> class C { int a = 1000; int f() => a * 3; } >>> c.f() 30

Remark: This follows the Dart hot reload semantics.

Extends and implements

>>> class A { int f() => 10; } class B extends A { int f() => 20; } >>> A().f() + B().f() 30 >>> class B implements A { int f() => 30; } >>> A().f() + B().f() 40

Use local variables, fields, and global variables

>>> a = 10; >>> class C { int b = 20; int f() { int c = 30; a++; b++; c++; return a+b+c; } } >>> c = C(); print(c.f()); print(c.f()); 63 65

Add libraries as dependency

Use !dart pub add package_name, just like what is done in Python (Jupyter/IPython).

>>> join('directory', 'file.txt') (...error, since have not added that dependency...) >>> !dart pub add path Resolving dependencies... + path 1.8.2 Changed 1 dependency! >>> join('directory', 'file.txt') (...error, since have imported it...) >>> import 'package:path/path.dart'; >>> join('directory', 'file.txt') directory/file.txt

Imports

Built-in package

>>> Random().nextInt(100) (some error outputs here, because it is not imported) >>> import "dart:math"; >>> Random().nextInt(100) 9

Third party package

Note: If it has not been added to dependency, please follow instructions above and use !dart pub add path to add it.

>>> join('directory', 'file.txt') (...error, since have imported it...) >>> import 'package:path/path.dart'; >>> join('directory', 'file.txt') directory/file.txt

Multiple in one go

>>> int g() => 42; class C { int a = 10; int f() => a * 2; } >>> C().f() + g() 62

Multi line if not ended

(The ..., instead of >>>, appears in the two lines, because the package detects it is not finished.)

>>> class C { ... int a = 10; ... } >>>

Run commands

Use prefix !.

>>> !whoami tom >>> !date 2022-10-22 ...outputs...

Execute within environment of existing package

interactive --directory path/to/your/package

Implementation

General:

  • Create a blank package and an isolate as execution workspace
  • Extract imports/classes/functions/etc using analyzer, with replacing when it has the same name, and synthesize a dart file – thus supports rich Dart feature
  • Trigger Dart’s hot-reload after the dart file is updated
  • Use analyzer to distinguish expressions/statements/compilation-units and do corresponding transformation
  • The only thing to let Dart VM service to evaluate is generatedMethod(), and do not evaluate anything more
  • Adding dependencies is as simple as running standard shell command

As for “global” variables:

  • Indeed implemented by a field variable
  • Statements: Make it inside extension on dynamic { Object? generatedMethod() { ...the statements... } } to access it seamlessly
  • Functions: Convert functions to extension methods on dynamic to access it seamlessly
  • Classes: Synthesize getters/setters in classes, and delegate to the field variables, whenever there is a potential access to global variable to access it seamlessly

TODO more implementation discussions if people are interested (above is so brief)

✨ Contributors

All Contributors

Thanks goes to these wonderful people (emoji key):

fzyzcjyfzyzcjy? ? ? Vyacheslav EgorovVyacheslav Egorov? Andreas KirschAndreas Kirsch? Maksim LinMaksim Lin?

More specifically, thanks for all these contributions:

GitHub

View Github

Footer Example