REPL (interactive shell) for Dart, supporting 3rd party packages, hot reload, and full grammar
MIT License
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 7th highest-voted request). So here it comes!
A full-featured REPL (interactive shell), with:
>>> !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
>>> import 'a.dart';
>>> myFunc()
hello, tom
// ... change content of `a.dart` ...
>>> myFunc()
hello, alex
>>> 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
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:
$ interactive --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();
Install (just standard procedure of installing global dart packages):
dart pub global activate interactive
Use (just a normal binary):
interactive
And play with it :)
>>> a = 'Hello'; b = ' world!';
>>> '$a, $b'
Hello, world!
>>> print(a)
Hello
(All methods, not only print
)
>>> String f() => 'old';
>>> f()
old
>>> String f() => 'new';
>>> f()
new
>>> a = 10;
>>> int f() { int b = 20; a++; b++; return a+b; }
>>> f()
32
>>> f()
33
>>> 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.
>>> 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
>>> 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
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
>>> Random().nextInt(100)
(some error outputs here, because it is not imported)
>>> import "dart:math";
>>> Random().nextInt(100)
9
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
>>> int g() => 42; class C { int a = 10; int f() => a * 2; }
>>> C().f() + g()
62
(The ...
, instead of >>>
, appears in the two lines, because the package detects it is not finished.)
>>> class C {
... int a = 10;
... }
>>>
Use prefix !
.
>>> !whoami
tom
>>> !date
2022-10-22 ...outputs...
interactive --directory path/to/your/package
General:
generatedMethod()
, and do not evaluate anything moreAs for "global" variables:
extension on dynamic { Object? generatedMethod() { ...the statements... } }
to access it seamlesslyTODO more implementation discussions if people are interested (above is so brief)
Because of Dart's bug (https://github.com/dart-lang/sdk/issues/48329), the upstream cli_repl
package does not work well on Windows. The issues vary from terminal to terminal, but generally speaking, backspace doesn't work, we cannot move on the command line with arrows nor Ctrl+B/F, and no command history with arrows or ^P/^N either.
Since dart_interactive
depends on it, it is suggested to use WSL or *nix.
Command history is not yet saved between sessions. However, this is implementable, and feel free to create an issue or PR.
Hot reload failed
Currently, some user mistakes will produce Hot reload failed
error instead of the actual error. It will break the next command or the whole REPL. If you can't evaluate something simple as "1" after two tries, you can restart quickly with Ctrl/Cmd+D, Up Arrow and Enter in most terminals.
>>> 1
1
>>> print() // <----- oops, argument is not optional
[WARNING 2024-03-13 01:50:18.419137] Error: Hot reload failed, maybe because code has syntax error?
>>> 1
[WARNING 2024-03-13 01:50:20.464239] Error: Hot reload failed, maybe because code has syntax error?
>>> 1
[WARNING 2024-03-13 01:50:20.464239] Error: Hot reload failed, maybe because code has syntax error?
Thanks goes to these wonderful people (emoji key):
More specifically, thanks for all these contributions: