├── LICENSE
├── CHANGELOG.md
├── .circleci
└── config.yml
├── .metadata
├── .gitignore
├── pubspec.yaml
├── README.md
├── pubspec.lock
├── test
└── flutterscript_test.dart
└── lib
├── flutterscript.dart
└── lisp.dart
/LICENSE:
--------------------------------------------------------------------------------
1 | TODO: Add your license here.
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [0.0.1] - TODO: Add release date.
2 |
3 | * TODO: Describe initial release.
4 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: cirrusci/flutter
6 | steps:
7 | - checkout
8 | - run: flutter packages get
9 | - run: flutter test
10 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b
8 | channel: stable
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # Visual Studio Code related
19 | .vscode/
20 |
21 | # Flutter/Dart/Pub related
22 | **/doc/api/
23 | .dart_tool/
24 | .flutter-plugins
25 | .packages
26 | .pub-cache/
27 | .pub/
28 | build/
29 | **/*.reflectable.dart
30 |
31 | # Android related
32 | **/android/**/gradle-wrapper.jar
33 | **/android/.gradle
34 | **/android/captures/
35 | **/android/gradlew
36 | **/android/gradlew.bat
37 | **/android/local.properties
38 | **/android/**/GeneratedPluginRegistrant.java
39 |
40 | # iOS/XCode related
41 | **/ios/**/*.mode1v3
42 | **/ios/**/*.mode2v3
43 | **/ios/**/*.moved-aside
44 | **/ios/**/*.pbxuser
45 | **/ios/**/*.perspectivev3
46 | **/ios/**/*sync/
47 | **/ios/**/.sconsign.dblite
48 | **/ios/**/.tags*
49 | **/ios/**/.vagrant/
50 | **/ios/**/DerivedData/
51 | **/ios/**/Icon?
52 | **/ios/**/Pods/
53 | **/ios/**/.symlinks/
54 | **/ios/**/profile
55 | **/ios/**/xcuserdata
56 | **/ios/.generated/
57 | **/ios/Flutter/App.framework
58 | **/ios/Flutter/Flutter.framework
59 | **/ios/Flutter/Generated.xcconfig
60 | **/ios/Flutter/app.flx
61 | **/ios/Flutter/app.zip
62 | **/ios/Flutter/flutter_assets/
63 | **/ios/ServiceDefinitions.json
64 | **/ios/Runner/GeneratedPluginRegistrant.*
65 |
66 | # Exceptions to above rules.
67 | !**/ios/**/default.mode1v3
68 | !**/ios/**/default.mode2v3
69 | !**/ios/**/default.pbxuser
70 | !**/ios/**/default.perspectivev3
71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
72 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutterscript
2 | description: An embeddable interpreter for Flutter Application
3 | version: 0.0.1
4 | author:
5 | homepage:
6 |
7 | environment:
8 | sdk: ">=2.1.0 <3.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | dev_dependencies:
15 | flutter_test:
16 | sdk: flutter
17 |
18 | # For information on the generic Dart part of this file, see the
19 | # following page: https://www.dartlang.org/tools/pub/pubspec
20 |
21 | # The following section is specific to Flutter.
22 | flutter:
23 |
24 | # To add assets to your package, add an assets section, like this:
25 | # assets:
26 | # - images/a_dot_burr.jpeg
27 | # - images/a_dot_ham.jpeg
28 | #
29 | # For details regarding assets in packages, see
30 | # https://flutter.dev/assets-and-images/#from-packages
31 | #
32 | # An image asset can refer to one or more resolution-specific "variants", see
33 | # https://flutter.dev/assets-and-images/#resolution-aware.
34 |
35 | # To add custom fonts to your package, add a fonts section here,
36 | # in this "flutter" section. Each entry in this list should have a
37 | # "family" key with the font family name, and a "fonts" key with a
38 | # list giving the asset and other descriptors for the font. For
39 | # example:
40 | # fonts:
41 | # - family: Schyler
42 | # fonts:
43 | # - asset: fonts/Schyler-Regular.ttf
44 | # - asset: fonts/Schyler-Italic.ttf
45 | # style: italic
46 | # - family: Trajan Pro
47 | # fonts:
48 | # - asset: fonts/TrajanPro.ttf
49 | # - asset: fonts/TrajanPro_Bold.ttf
50 | # weight: 700
51 | #
52 | # For details regarding fonts in packages, see
53 | # https://flutter.dev/custom-fonts/#from-packages
54 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # flutterscript
2 |
3 | [](https://circleci.com/gh/cowboyd/flutterscript)
4 |
5 | An embeddable interpreter for Flutter applications
6 |
7 |
8 |
9 | ## Getting Started
10 |
11 | ``` dart
12 | import "package:flutterscript/flutterscript.dart";
13 |
14 | main() async {
15 | FlutterScript interpreter = await FlutterScript.create();
16 | await interpreter.eval('"Hello World"'); //> "Hello World"
17 | }
18 | ```
19 |
20 | You can embed dart functions into the interpreter using the `defn`
21 | method:
22 |
23 | ``` dart
24 | await interpreter.defn("loud", (DartArguments arguments) {
25 | String input = arguments.positional.first.toString();
26 | return "${input.toUpperCase()}!";
27 | });
28 |
29 | await interpreter.eval('(loud "Hello World")'); //> "HELLO WORLD!";
30 | ```
31 |
32 | To embed a _class_ into the interpreter, you use the `defClass` method
33 | where you give it a constructor function, and a list of methods on
34 | that class.
35 |
36 | ``` dart
37 | await interpreter.defClass("Text", (DartArguments args) => Text(args[0]), {
38 | "data": (text, __) => text.data,
39 | "toString": (text, __) => "Text(${text.data})"
40 | })
41 |
42 | await interpreter.eval('(setq text (Text "Hello World"))');
43 | await interpreter.eval('(-> text data)') //> "Hello World";
44 | await interpreter.eval('(-> text toString)') //> "Text(Hello World)";
45 | ```
46 |
47 | The door swings boths ways as well. Not only can you embed dart
48 | functions into FlutterScript and call them from FlutterScript code,
49 | but you can also bring FlutterScript functions into Dart and call them
50 | from Dart code:
51 |
52 | ``` dart
53 | FlutterScriptFn add = await interpreter.eval('(=> (x y) (+ x y))');
54 | add([10, 7]); //=> 17
55 | ```
56 |
57 | ## Development
58 |
59 |
60 | ``` shell
61 | $ flutter packages get
62 | $ flutter test
63 | ```
64 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.dartlang.org"
9 | source: hosted
10 | version: "2.1.0"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.dartlang.org"
16 | source: hosted
17 | version: "1.0.4"
18 | charcode:
19 | dependency: transitive
20 | description:
21 | name: charcode
22 | url: "https://pub.dartlang.org"
23 | source: hosted
24 | version: "1.1.2"
25 | collection:
26 | dependency: transitive
27 | description:
28 | name: collection
29 | url: "https://pub.dartlang.org"
30 | source: hosted
31 | version: "1.14.11"
32 | flutter:
33 | dependency: "direct main"
34 | description: flutter
35 | source: sdk
36 | version: "0.0.0"
37 | flutter_test:
38 | dependency: "direct dev"
39 | description: flutter
40 | source: sdk
41 | version: "0.0.0"
42 | matcher:
43 | dependency: transitive
44 | description:
45 | name: matcher
46 | url: "https://pub.dartlang.org"
47 | source: hosted
48 | version: "0.12.5"
49 | meta:
50 | dependency: transitive
51 | description:
52 | name: meta
53 | url: "https://pub.dartlang.org"
54 | source: hosted
55 | version: "1.1.6"
56 | path:
57 | dependency: transitive
58 | description:
59 | name: path
60 | url: "https://pub.dartlang.org"
61 | source: hosted
62 | version: "1.6.2"
63 | pedantic:
64 | dependency: transitive
65 | description:
66 | name: pedantic
67 | url: "https://pub.dartlang.org"
68 | source: hosted
69 | version: "1.5.0"
70 | quiver:
71 | dependency: transitive
72 | description:
73 | name: quiver
74 | url: "https://pub.dartlang.org"
75 | source: hosted
76 | version: "2.0.2"
77 | sky_engine:
78 | dependency: transitive
79 | description: flutter
80 | source: sdk
81 | version: "0.0.99"
82 | source_span:
83 | dependency: transitive
84 | description:
85 | name: source_span
86 | url: "https://pub.dartlang.org"
87 | source: hosted
88 | version: "1.5.5"
89 | stack_trace:
90 | dependency: transitive
91 | description:
92 | name: stack_trace
93 | url: "https://pub.dartlang.org"
94 | source: hosted
95 | version: "1.9.3"
96 | stream_channel:
97 | dependency: transitive
98 | description:
99 | name: stream_channel
100 | url: "https://pub.dartlang.org"
101 | source: hosted
102 | version: "2.0.0"
103 | string_scanner:
104 | dependency: transitive
105 | description:
106 | name: string_scanner
107 | url: "https://pub.dartlang.org"
108 | source: hosted
109 | version: "1.0.4"
110 | term_glyph:
111 | dependency: transitive
112 | description:
113 | name: term_glyph
114 | url: "https://pub.dartlang.org"
115 | source: hosted
116 | version: "1.1.0"
117 | test_api:
118 | dependency: transitive
119 | description:
120 | name: test_api
121 | url: "https://pub.dartlang.org"
122 | source: hosted
123 | version: "0.2.4"
124 | typed_data:
125 | dependency: transitive
126 | description:
127 | name: typed_data
128 | url: "https://pub.dartlang.org"
129 | source: hosted
130 | version: "1.1.6"
131 | vector_math:
132 | dependency: transitive
133 | description:
134 | name: vector_math
135 | url: "https://pub.dartlang.org"
136 | source: hosted
137 | version: "2.0.8"
138 | sdks:
139 | dart: ">=2.2.0 <3.0.0"
140 |
--------------------------------------------------------------------------------
/test/flutterscript_test.dart:
--------------------------------------------------------------------------------
1 | import "package:flutter_test/flutter_test.dart";
2 |
3 | import "package:flutterscript/flutterscript.dart";
4 | import "package:flutter/widgets.dart";
5 |
6 | void main() {
7 |
8 | FlutterScript interp;
9 | var eval = (String source) async {
10 | return await interp.eval(source);
11 | };
12 |
13 | setUp(() async {
14 | interp = await FlutterScript.create();
15 | });
16 |
17 |
18 | test("1 evals to 1", () async {
19 | expect(await interp.eval("1"), equals(1));
20 | });
21 |
22 | group("List inter-op", () {
23 | test("can instantiate a dart List from within Lisp", () async {
24 | List list = await eval('(List 1 2 "three")');
25 | expect(list[0], equals(1));
26 | expect(list[1], equals(2));
27 | expect(list[2], equals("three"));
28 | });
29 | });
30 |
31 | group("Map inter-op", () {
32 | test("can instantiate a dart map from within Lisp", () async {
33 | Map map = await eval('(Map "one" 1 "two" 2)');
34 | expect(map["one"], equals(1));
35 | expect(map["two"], equals(2));
36 | });
37 | });
38 |
39 | group("Raw Dart inter-op", () {
40 |
41 |
42 | setUp(() async {
43 | await interp.addClass("App", appType);
44 |
45 | await eval('(setq app (dart/funcall "App" (dart/arguments (List) (Map "title" "Flutter Demo" "theme" "Dark"))))');
46 | });
47 |
48 | test("can create a new instance of an object with named parameters", () async {
49 | App app = await eval('app');
50 | expect(app.title, equals("Flutter Demo"));
51 | expect(app.theme, equals("Dark"));
52 | });
53 |
54 | test("can call methods on objects", () async {
55 | expect(await eval("(dart/methodcall app 'identityMethod (dart/arguments (List 5) (Map)))"), equals(5));
56 | });
57 |
58 | test("can call methods with optional positional parameters", () async {
59 | expect(await eval("(dart/methodcall app 'withOptionalPositionalParameters (dart/arguments (List 1 2) (Map)))"), equals([1, 2]));
60 | });
61 |
62 | test("can call methods with optional named parameters", () async {
63 | expect(await eval('(dart/methodcall app \'withOptionalNamedParameters (dart/arguments (List 1) (Map "two" 2)))'),
64 | equals({"one": 1, "two": 2}));
65 | });
66 |
67 |
68 | test("can get fields from an object", () async {
69 | expect(await eval("(dart/methodcall app 'title (dart/arguments (List) (Map)))"), equals("Flutter Demo"));
70 | expect(await eval("(dart/methodcall app 'theme (dart/arguments (List) (Map)))"), equals("Dark"));
71 | });
72 | });
73 |
74 | group("Friendly Dart-like syntax inter-op", () {
75 | setUp(() async {
76 | await interp.addClass("App", appType);
77 |
78 | await eval('(setq app (App title: "Flutter Demo" theme: "Dark"))');
79 | });
80 |
81 | test("can call methods on object with nice dart-like syntax", () async {
82 | App app = await eval('app');
83 | expect(app.title, equals("Flutter Demo"));
84 | expect(app.theme, equals("Dark"));
85 | });
86 |
87 | test("can call methods with optional positional parameters using dart-like syntax", () async {
88 | var result = await eval('(-> app withOptionalPositionalParameters 1 2)');
89 | expect(result, equals([1, 2]));
90 | });
91 |
92 | test("can call methods with optional named parameters using friendly dart-like syntax", () async {
93 | expect(await eval('(-> app withOptionalNamedParameters 1 two: 2)'),
94 | equals({"one": 1, "two": 2}));
95 | });
96 |
97 | test("can access properties with method access", () async {
98 | expect(await eval('(-> app title)'), equals("Flutter Demo"));
99 | });
100 | });
101 |
102 | group("Flutter inter-op", () {
103 | setUp(() async {
104 | await interp.defineClass("Text", (DartArguments args) => Text(args[0]), {
105 | "data": (text, _) => text.data
106 | });
107 | });
108 | test("can actually instantiate and call methods on flutter widgets",() async {
109 | Text text = await eval('(Text "hello world")');
110 | String data = await eval('(-> (Text "hello world") data)');
111 | expect(text.data, equals("hello world"));
112 | expect(data, equals("hello world"));
113 | });
114 | });
115 |
116 | group("FlutterScript inter-op", () {
117 |
118 | test("allows flutterscript functions to be referenced and called from dart", () async {
119 | FlutterScriptFn add = await eval('(flutterscript/function (lambda (x y) (+ x y)))');
120 | expect(add([1, 2]), equals(3));
121 | });
122 |
123 | test("allows flutterscript functions to be defined with a nice syntax", () async {
124 | FlutterScriptFn add = await eval('(=> (x y) (+ x y))');
125 | expect(add([5, 5]), equals(10));
126 | });
127 |
128 | test("still lets you call dart-callable functions from flutterscript", () async {
129 | var result = await eval('(let ((add (=> (x y) (+ x y)))) (add 40 2))');
130 | expect(result, equals(42));
131 | });
132 | });
133 | }
134 |
135 | class App {
136 | String title;
137 | String theme;
138 |
139 | App({this.title, this.theme});
140 |
141 | identityMethod(value) {
142 | return value;
143 | }
144 |
145 | withOptionalPositionalParameters (one, [two]) {
146 | return [one, two];
147 | }
148 |
149 | withOptionalNamedParameters(one, { two }) {
150 | return {"one": one, "two": two };
151 | }
152 | }
153 |
154 | DartClass appType = DartClass((DartArguments args) => App(title: args["title"], theme: args["theme"]), {
155 | "title": (app, _) => app.title,
156 | "theme": (app, _) => app.theme,
157 | "identityMethod": (app, arguments) => app.identityMethod(arguments[0]),
158 | "withOptionalPositionalParameters": (app, arguments) => app.withOptionalPositionalParameters(arguments[0], arguments[1]),
159 | "withOptionalNamedParameters": (app, arguments) => app.withOptionalNamedParameters(arguments[0], two: arguments["two"])
160 | });
161 |
--------------------------------------------------------------------------------
/lib/flutterscript.dart:
--------------------------------------------------------------------------------
1 | library flutterscript;
2 |
3 | import "dart:convert";
4 | import "dart:async";
5 | import "lisp.dart";
6 |
7 | typedef DartFn = Object Function(DartArguments arguments);
8 | typedef DartMethod = Object Function(dynamic invocant, DartArguments arguments);
9 |
10 | class DartClass {
11 | DartFn constructor;
12 | Map methods;
13 | DartClass(this.constructor, this.methods);
14 | }
15 |
16 | class DartArguments {
17 | List