├── 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 | [![CircleCI](https://circleci.com/gh/cowboyd/flutterscript.svg?style=shield)](https://circleci.com/gh/cowboyd/flutterscript) 4 | 5 | An embeddable interpreter for Flutter applications 6 | 7 | Demo of FlutterScript 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 positional; 18 | Map named; 19 | 20 | DartArguments(List input) { 21 | List positions = input.first; 22 | Map names = input.last; 23 | 24 | if (positions == null) { 25 | this.positional = []; 26 | } else { 27 | this.positional = positions; 28 | } 29 | if (names == null) { 30 | this.named = {}; 31 | } else { 32 | this.named = names; 33 | } 34 | } 35 | 36 | operator [](Object key) { 37 | if (key is Symbol || key is String) { 38 | return named[key.toString()]; 39 | } else if (key is int) { 40 | return positional[key]; 41 | } 42 | } 43 | 44 | String toString() { 45 | return "DartArguments(${this.positional}, ${this.named})"; 46 | } 47 | } 48 | 49 | class FlutterScriptFn extends Closure { 50 | Interp lisp; 51 | 52 | FlutterScriptFn(this.lisp, Closure lambda): super(lambda.carity, lambda.body, lambda.env); 53 | 54 | Object call(List arguments) { 55 | Cell functionApplication = Cell(this, null); 56 | Cell tail = functionApplication; 57 | arguments.forEach((argument) { 58 | tail.cdr = Cell(argument, null); 59 | tail = tail.cdr; 60 | }); 61 | return lisp.eval(functionApplication, null); 62 | } 63 | } 64 | 65 | 66 | class FlutterScript { 67 | Interp lisp; 68 | Map fns; 69 | Map> methodsOf; 70 | 71 | static Future create() async { 72 | Interp lisp = await makeInterp(); 73 | FlutterScript interpreter = FlutterScript(lisp); 74 | 75 | await interpreter.eval(""" 76 | (defmacro -> (invocant name &rest args) 77 | `(dart/methodcall ,invocant (quote ,name) (dart/parameters ,@args))) 78 | """); 79 | 80 | await interpreter.eval(""" 81 | (defmacro => (args body) 82 | `(flutterscript/function (lambda ,args ,body))) 83 | """); 84 | return interpreter; 85 | } 86 | 87 | FlutterScript(this.lisp) { 88 | fns = {}; 89 | methodsOf = {}; 90 | 91 | lisp.globals[Sym("dart/parameters")] = DartParameters(); 92 | 93 | lisp.def("dart/arguments", 2, (List arguments) { 94 | return DartArguments(arguments); 95 | }); 96 | 97 | lisp.def("dart/funcall", 2, (List arguments) { 98 | String functionName = arguments.first; 99 | DartFn fn = fns[functionName]; 100 | if (fn == null) { 101 | throw new Exception("void function: `$functionName`"); 102 | } 103 | DartArguments args = arguments[1]; 104 | 105 | return fn(args); 106 | }); 107 | 108 | lisp.def("flutterscript/function", 1, (List arguments) { 109 | var lambda = arguments.first; 110 | if (lambda is Closure) { 111 | return FlutterScriptFn(lisp, lambda); 112 | } else { 113 | throw new ArgumentError("argument to flutterscript/function must be a function, not `lambda`"); 114 | } 115 | }); 116 | 117 | lisp.def("dart/methodcall", 3, (List arguments) { 118 | Object invocant = arguments.first; 119 | Map methods = methodsOf[invocant.runtimeType]; 120 | String methodName = arguments[1].toString(); 121 | 122 | DartMethod method = methods[methodName]; 123 | if (method == null) { 124 | throw new Exception("no such method `$methodName` on `$invocant`"); 125 | } 126 | DartArguments args = arguments[2]; 127 | return method(invocant, args); 128 | }); 129 | 130 | lisp.def("List", -1, (List args) { 131 | List result = new List(); 132 | for (var cell = args.first; cell != null; cell = cell.cdr) { 133 | result.add(cell.car); 134 | } 135 | return result; 136 | }); 137 | 138 | lisp.def("Map", -1, (List args) { 139 | int index = 0; 140 | String key; 141 | List> entries = new List>(); 142 | for (var cell = args.first; cell != null; cell = cell.cdr) { 143 | if (index % 2 == 0) { 144 | key = cell.car; 145 | } else { 146 | entries.add(MapEntry(key.toString(), cell.car)); 147 | } 148 | index++; 149 | } 150 | return Map.fromEntries(entries); 151 | }); 152 | } 153 | 154 | 155 | defn(String functionName, DartFn function) async { 156 | fns[functionName] = function; 157 | return await eval(""" 158 | (defmacro $functionName (&rest args) 159 | `(dart/funcall \"$functionName\" (dart/parameters ,@args))) 160 | """); 161 | } 162 | 163 | defineType(Type type, Map methods) { 164 | if (methodsOf[type] == null) { 165 | methodsOf[type] = methods; 166 | } 167 | } 168 | 169 | addClass(String name, DartClass type) { 170 | defineClass(name, type.constructor, type.methods); 171 | } 172 | 173 | defineClass(String name, DartFn constructor, Map methods) { 174 | defn(name, (DartArguments arguments) { 175 | Object instance = constructor(arguments); 176 | 177 | defineType(instance.runtimeType, methods); 178 | 179 | return instance; 180 | }); 181 | } 182 | 183 | Future eval(String source) async { 184 | Stream input = Stream.fromFuture(Future(() => source)); 185 | input = input.transform(const LineSplitter()); 186 | var lines = new StreamIterator(input); 187 | var reader = new Reader(lines); 188 | var sExp = await reader.read(); 189 | return lisp.eval(sExp, null); 190 | } 191 | } 192 | 193 | 194 | class DartParameters extends Macro { 195 | DartParameters(): super(-1, null); 196 | 197 | @override Cell expandWith(interpreter, Cell arg) { 198 | Sym mapSym = Sym.table["Map"]; 199 | Sym listSym = Sym.table["List"]; 200 | Sym argumentsSym = Sym.table["dart/arguments"]; 201 | 202 | List positional = new List(); 203 | List> named = new List(); 204 | Sym key = null; 205 | 206 | foldl(arg, arg, (body, item) { 207 | if (item is Sym && item.name.endsWith(":")) { 208 | if (key != null) { 209 | throw new Exception("expected value for key $key, but got another key: $item"); 210 | } else { 211 | key = item; 212 | } 213 | } else { 214 | if (key != null) { 215 | named.add(new MapEntry(key, item)); 216 | key = null; 217 | } else { 218 | positional.add(item); 219 | } 220 | } 221 | return body; 222 | }); 223 | 224 | Cell pbody = Cell(listSym, null); 225 | Cell pcur = pbody; 226 | positional.forEach((item) { 227 | pcur.cdr = Cell(item, null); 228 | pcur = pcur.cdr; 229 | }); 230 | Cell nbody = Cell(mapSym, null); 231 | Cell ncur = nbody; 232 | named.forEach((entry) { 233 | String keyName = entry.key.name; 234 | 235 | ncur.cdr = Cell(keyName.substring(0, keyName.length - 1), Cell(entry.value, null)); 236 | ncur = ncur.cdr.cdr; 237 | }); 238 | Cell result = Cell(argumentsSym, Cell(pbody, Cell(nbody, null))); 239 | return result; 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /lib/lisp.dart: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dart 2 | // Nukata Lisp 2.00.0 in Dart 2.5 (H27.03.16/R01.11.02) by SUZUKI Hisao 3 | 4 | import "dart:async"; 5 | import "dart:convert"; 6 | import "dart:io"; 7 | 8 | const intBits = 63; // 53 for dart2js 9 | 10 | /// Converts [a] into an int if possible. 11 | normalize(BigInt a) => (a.bitLength <= intBits) ? a.toInt() : a; 12 | 13 | /// Is [a] a number? 14 | bool isNumber(a) => a is num || a is BigInt; 15 | 16 | /// Calculates [a] + [b]. 17 | add(a, b) { 18 | if (a is int) { 19 | if (b is int) { 20 | if (a.bitLength < intBits && b.bitLength < intBits) { 21 | return a + b; 22 | } else { 23 | return normalize(BigInt.from(a) + BigInt.from(b)); 24 | } 25 | } else if (b is double) { 26 | return a + b; 27 | } else if (b is BigInt) { 28 | return normalize(BigInt.from(a) + b); 29 | } 30 | } else if (a is double) { 31 | if (b is num) { 32 | return a + b; 33 | } else if (b is BigInt) { 34 | return a + b.toDouble(); 35 | } 36 | } else if (a is BigInt) { 37 | if (b is int) { 38 | return normalize(a + BigInt.from(b)); 39 | } else if (b is double) { 40 | return a.toDouble() + b; 41 | } else if (b is BigInt) { 42 | return normalize(a + b); 43 | } 44 | } 45 | throw ArgumentError("$a, $b"); 46 | } 47 | 48 | /// Calculates [a] - [b]. 49 | subtract(a, b) { 50 | if (a is int) { 51 | if (b is int) { 52 | if (a.bitLength < intBits && b.bitLength < intBits) { 53 | return a - b; 54 | } else { 55 | return normalize(BigInt.from(a) - BigInt.from(b)); 56 | } 57 | } else if (b is double) { 58 | return a - b; 59 | } else if (b is BigInt) { 60 | return normalize(BigInt.from(a) - b); 61 | } 62 | } else if (a is double) { 63 | if (b is num) { 64 | return a - b; 65 | } else if (b is BigInt) { 66 | return a - b.toDouble(); 67 | } 68 | } else if (a is BigInt) { 69 | if (b is int) { 70 | return normalize(a - BigInt.from(b)); 71 | } else if (b is double) { 72 | return a.toDouble() - b; 73 | } else if (b is BigInt) { 74 | return normalize(a - b); 75 | } 76 | } 77 | throw ArgumentError("$a, $b"); 78 | } 79 | 80 | /// Compares [a] and [b]. 81 | /// Returns -1, 0 or 1 as [a] is less than, equal to, or greater than [b]. 82 | num compare(a, b) { 83 | if (a is int) { 84 | if (b is int) { 85 | if (a.bitLength < intBits && b.bitLength < intBits) { 86 | return (a - b).sign; 87 | } else { 88 | return (BigInt.from(a) - BigInt.from(b)).sign; 89 | } 90 | } else if (b is double) { 91 | return (a - b).sign; 92 | } else if (b is BigInt) { 93 | return (BigInt.from(a) - b).sign; 94 | } 95 | } else if (a is double) { 96 | if (b is num) { 97 | return (a - b).sign; 98 | } else if (b is BigInt) { 99 | return (a - b.toDouble()).sign; 100 | } 101 | } else if (a is BigInt) { 102 | if (b is int) { 103 | return (a - BigInt.from(b)).sign; 104 | } else if (b is double) { 105 | return (a.toDouble() - b).sign; 106 | } else if (b is BigInt) { 107 | return (a - b).sign; 108 | } 109 | } 110 | throw ArgumentError("$a, $b"); 111 | } 112 | 113 | /// Calculates [a] * [b]. 114 | multiply(a, b) { 115 | if (a is int) { 116 | if (b is int) { 117 | if (a.bitLength + b.bitLength < intBits) { 118 | return a * b; 119 | } else { 120 | return normalize(BigInt.from(a) * BigInt.from(b)); 121 | } 122 | } else if (b is double) { 123 | return a * b; 124 | } else if (b is BigInt) { 125 | return BigInt.from(a) * b; 126 | } 127 | } else if (a is double) { 128 | if (b is num) { 129 | return a * b; 130 | } else if (b is BigInt) { 131 | return a * b.toDouble(); 132 | } 133 | } else if (a is BigInt) { 134 | if (b is int) { 135 | return a * BigInt.from(b); 136 | } else if (b is double) { 137 | return a.toDouble() * b; 138 | } else if (b is BigInt) { 139 | return a * b; 140 | } 141 | } 142 | throw ArgumentError("$a, $b"); 143 | } 144 | 145 | /// Calculates [a] / [b] (rounded quotient). 146 | double divide(a, b) { 147 | if (a is int) { 148 | if (b is num) { 149 | return a / b; 150 | } else if (b is BigInt) { 151 | return BigInt.from(a) / b; 152 | } 153 | } else if (a is double) { 154 | if (b is num) { 155 | return a / b; 156 | } else if (b is BigInt) { 157 | return a / b.toDouble(); 158 | } 159 | } else if (a is BigInt) { 160 | if (b is int) { 161 | return a / BigInt.from(b); 162 | } else if (b is double) { 163 | return a.toDouble() / b; 164 | } else if (b is BigInt) { 165 | return a / b; 166 | } 167 | } 168 | throw ArgumentError("$a, $b"); 169 | } 170 | 171 | /// Calculates the quotient of [a] and [b]. 172 | quotient(a, b) { 173 | if (a is int) { 174 | if (b is num) { 175 | return a ~/ b; 176 | } else if (b is BigInt) { 177 | return normalize(BigInt.from(a) ~/ b); 178 | } 179 | } else if (a is double) { 180 | if (b is num) { 181 | return a ~/ b; 182 | } else if (b is BigInt) { 183 | return a ~/ b.toDouble(); 184 | } 185 | } else if (a is BigInt) { 186 | if (b is int) { 187 | return normalize(a ~/ BigInt.from(b)); 188 | } else if (b is double) { 189 | return a.toDouble() ~/ b; 190 | } else if (b is BigInt) { 191 | return normalize(a ~/ b); 192 | } 193 | } 194 | throw ArgumentError("$a, $b"); 195 | } 196 | 197 | /// Calculates the remainder of the quotient of [a] and [b]. 198 | remainder(a, b) { 199 | if (a is int) { 200 | if (b is num) { 201 | return a.remainder(b); 202 | } else if (b is BigInt) { 203 | return normalize(BigInt.from(a).remainder(b)); 204 | } 205 | } else if (a is double) { 206 | if (b is num) { 207 | return a.remainder(b); 208 | } else if (b is BigInt) { 209 | return a.remainder(b.toDouble()); 210 | } 211 | } else if (a is BigInt) { 212 | if (b is int) { 213 | return normalize(a.remainder(BigInt.from(b))); 214 | } else if (b is double) { 215 | return a.toDouble().remainder(b); 216 | } else if (b is BigInt) { 217 | return normalize(a.remainder(b)); 218 | } 219 | } 220 | throw ArgumentError("$a, $b"); 221 | } 222 | 223 | /// Tries to parse a string as an int, a BigInt or a double. 224 | /// Returns null if [s] was not parsed successfully. 225 | tryParse(String s) { 226 | var r = BigInt.tryParse(s); 227 | return (r == null) ? double.tryParse(s) : normalize(r); 228 | } 229 | 230 | //---------------------------------------------------------------------- 231 | 232 | /// Cons cell 233 | class Cell { 234 | var car; 235 | var cdr; 236 | 237 | Cell(this.car, this.cdr); 238 | @override String toString() => "($car . $cdr)"; 239 | 240 | /// Length as a list 241 | int get length => foldl(0, this, (i, e) => i + 1); 242 | } 243 | 244 | 245 | /// mapcar((a b c), fn) => (fn(a) fn(b) fn(c)) 246 | Cell mapcar(Cell j, fn(x)) { 247 | if (j == null) 248 | return null; 249 | var a = fn(j.car); 250 | var d = j.cdr; 251 | if (d is Cell) 252 | d = mapcar(d, fn); 253 | if (identical(j.car, a) && identical(j.cdr, d)) 254 | return j; 255 | return Cell(a, d); 256 | } 257 | 258 | /// foldl(x, (a b c), fn) => fn(fn(fn(x, a), b), c) 259 | foldl(x, Cell j, fn(y, z)) { 260 | while (j != null) { 261 | x = fn(x, j.car); 262 | j = j.cdr; 263 | } 264 | return x; 265 | } 266 | 267 | 268 | /// Lisp symbol 269 | class Sym { 270 | final String name; 271 | 272 | /// Constructs a symbol that is not interned. 273 | Sym.internal(this.name); 274 | 275 | @override String toString() => name; 276 | @override int get hashCode => name.hashCode; // Key to Speed for Dart 277 | 278 | /// The table of interned symbols 279 | static final Map table = {}; 280 | 281 | /// Constructs an interned symbol. 282 | /// Constructs a [Keyword] if [isKeyword] holds. 283 | factory Sym(String name, [bool isKeyword=false]) { 284 | var result = table[name]; 285 | assert(result == null || ! isKeyword); 286 | if (result == null) { 287 | result = isKeyword ? new Keyword.internal(name) : new Sym.internal(name); 288 | table[name] = result; 289 | } 290 | return result; 291 | } 292 | 293 | /// Is it interned? 294 | bool get isInterned => identical(this, table[name]); 295 | } 296 | 297 | 298 | /// Expression keyword 299 | class Keyword extends Sym { 300 | Keyword.internal(String name): super.internal(name); 301 | factory Keyword(String name) => Sym(name, true); 302 | } 303 | 304 | 305 | final Sym backQuoteSym = Sym("`"); 306 | final Sym commaAtSym = Sym(",@"); 307 | final Sym commaSym = Sym(","); 308 | final Sym dotSym = Sym("."); 309 | final Sym leftParenSym = Sym("("); 310 | final Sym rightParenSym = Sym(")"); 311 | final Sym singleQuoteSym = Sym("'"); 312 | 313 | final Sym appendSym = Sym("append"); 314 | final Sym consSym = Sym("cons"); 315 | final Sym listSym = Sym("list"); 316 | final Sym restSym = Sym("&rest"); 317 | final Sym unquoteSym = Sym("unquote"); 318 | final Sym unquoteSplicingSym = Sym("unquote-splicing"); 319 | 320 | //---------------------------------------------------------------------- 321 | 322 | /// Common base class of Lisp functions 323 | abstract class Func { 324 | /// Number of arguments, made negative if the function has &rest 325 | final int carity; 326 | 327 | int get arity => (carity < 0) ? -carity : carity; 328 | bool get hasRest => (carity < 0); 329 | int get fixedArgs => (carity < 0) ? -carity - 1 : carity; // # of fixed args. 330 | 331 | Func(this.carity); 332 | 333 | /// Makes a frame for local variables from a list of actual arguments. 334 | List makeFrame(Cell arg) { 335 | List frame = List(arity); 336 | int n = fixedArgs; 337 | int i; 338 | for (i = 0; i < n && arg != null; i++) { // Sets the list of fixed args. 339 | frame[i] = arg.car; 340 | arg = cdrCell(arg); 341 | } 342 | if (i != n || (arg != null && !hasRest)) 343 | throw EvalException("arity not matched", this); 344 | if (hasRest) 345 | frame[n] = arg; 346 | return frame; 347 | } 348 | 349 | /// Evaluates each expression in a frame. 350 | void evalFrame(List frame, Interp interp, Cell env) { 351 | int n = fixedArgs; 352 | for (int i = 0; i < n; i++) 353 | frame[i] = interp.eval(frame[i], env); 354 | if (hasRest && frame[n] is Cell) { 355 | Cell z = null; 356 | Cell y = null; 357 | for (Cell j = frame[n]; j != null; j = cdrCell(j)) { 358 | var e = interp.eval(j.car, env); 359 | Cell x = Cell(e, null); 360 | if (z == null) 361 | z = x; 362 | else 363 | y.cdr = x; 364 | y = x; 365 | } 366 | frame[n] = z; 367 | } 368 | } 369 | } 370 | 371 | 372 | /// Common base class of functions which are defined with Lisp expressions 373 | abstract class DefinedFunc extends Func { 374 | /// Lisp list as the function body 375 | final Cell body; 376 | 377 | DefinedFunc(int carity, this.body): super(carity); 378 | } 379 | 380 | 381 | /// Common function type which represents any factory method of DefinedFunc 382 | typedef DefinedFunc FuncFactory(int cariy, Cell body, Cell env); 383 | 384 | 385 | /// Compiled macro expression 386 | class Macro extends DefinedFunc { 387 | Macro(int carity, Cell body): super(carity, body); 388 | @override String toString() => "#"; 389 | 390 | /// Expands the macro with a list of actual arguments. 391 | expandWith(Interp interp, Cell arg) { 392 | List frame = makeFrame(arg); 393 | Cell env = Cell(frame, null); 394 | var x = null; 395 | for (Cell j = body; j != null; j = cdrCell(j)) 396 | x = interp.eval(j.car, env); 397 | return x; 398 | } 399 | 400 | static DefinedFunc make(int carity, Cell body, Cell env) { 401 | assert(env == null); 402 | return Macro(carity, body); 403 | } 404 | } 405 | 406 | 407 | /// Compiled lambda expression (Within another function) 408 | class Lambda extends DefinedFunc { 409 | Lambda(int carity, Cell body): super(carity, body); 410 | @override String toString() => "#"; 411 | 412 | static DefinedFunc make(int carity, Cell body, Cell env) { 413 | assert(env == null); 414 | return Lambda(carity, body); 415 | } 416 | } 417 | 418 | 419 | /// Compiled lambda expresssion (Closure with environment) 420 | class Closure extends DefinedFunc { 421 | /// The environment of the closure 422 | final Cell env; 423 | 424 | Closure(int carity, Cell body, this.env): super(carity, body); 425 | Closure.from(Lambda x, Cell env): this(x.carity, x.body, env); 426 | @override String toString() => "#"; 427 | 428 | /// Makes an environment to evaluate the body from a list of actual args. 429 | Cell makeEnv(Interp interp, Cell arg, Cell interpEnv) { 430 | List frame = makeFrame(arg); 431 | evalFrame(frame, interp, interpEnv); 432 | return Cell(frame, env); // Prepends the frame to the closure's env. 433 | } 434 | 435 | static DefinedFunc make(int carity, Cell body, Cell env) => 436 | Closure(carity, body, env); 437 | } 438 | 439 | 440 | /// Function type which represents any built-in function body 441 | typedef BuiltInFuncBody(List frame); 442 | 443 | /// Built-in function 444 | class BuiltInFunc extends Func { 445 | final String name; 446 | final BuiltInFuncBody body; 447 | 448 | BuiltInFunc(this.name, int carity, this.body): super(carity); 449 | @override String toString() => "#<$name:$carity>"; 450 | 451 | /// Invokes the built-in function with a list of actual arguments. 452 | evalWith(Interp interp, Cell arg, Cell interpEnv) { 453 | List frame = makeFrame(arg); 454 | evalFrame(frame, interp, interpEnv); 455 | try { 456 | return body(frame); 457 | } on EvalException catch (ex) { 458 | throw ex; 459 | } catch (ex) { 460 | throw EvalException("$ex -- $name", frame); 461 | } 462 | } 463 | } 464 | 465 | 466 | /// Bound variable in a compiled lambda/macro expression 467 | class Arg { 468 | final int level; 469 | final int offset; 470 | final Sym symbol; 471 | 472 | Arg(this.level, this.offset, this.symbol); 473 | @override String toString() => "#$level:$offset:$symbol"; 474 | 475 | /// Sets a value [x] to the location corresponding to the variable in [env]. 476 | void setValue(x, Cell env) { 477 | for (int i = 0; i < level; i++) 478 | env = env.cdr; 479 | env.car[offset] = x; 480 | } 481 | 482 | /// Gets a value from the location corresponding to the variable in [env]. 483 | getValue(Cell env) { 484 | for (int i = 0; i < level; i++) 485 | env = env.cdr; 486 | return env.car[offset]; 487 | } 488 | } 489 | 490 | 491 | /// Exception in evaluation 492 | class EvalException implements Exception { 493 | final String message; 494 | final List trace = []; 495 | 496 | EvalException(String msg, Object x, [bool quoteString=true]) 497 | : message = msg + ": " + str(x, quoteString); 498 | 499 | @override String toString() { 500 | var s = "EvalException: $message"; 501 | for (String line in trace) 502 | s += "\n\t$line"; 503 | return s; 504 | } 505 | } 506 | 507 | 508 | /// Exception which indicates an absence of a variable 509 | class NotVariableException extends EvalException { 510 | NotVariableException(x): super("variable expected", x); 511 | } 512 | 513 | //---------------------------------------------------------------------- 514 | 515 | /// Core of the interpreter 516 | class Interp { 517 | /// Table of the global values of symbols 518 | final Map globals = {}; 519 | 520 | /// Standard output of the interpreter 521 | StringSink cout = stdout; 522 | 523 | /// Sets built-in functions etc. as the global values of symbols. 524 | Interp() { 525 | def("car", 1, (List a) => (a[0] as Cell)?.car); 526 | def("cdr", 1, (List a) => (a[0] as Cell)?.cdr); 527 | def("cons", 2, (List a) => Cell(a[0], a[1])); 528 | def("atom", 1, (List a) => (a[0] is Cell) ? null : true); 529 | def("eq", 2, (List a) => identical(a[0], a[1]) ? true : null); 530 | 531 | def("list", -1, (List a) => a[0]); 532 | def("rplaca", 2, (List a) { a[0].car = a[1]; return a[1]; }); 533 | def("rplacd", 2, (List a) { a[0].cdr = a[1]; return a[1]; }); 534 | def("length", 1, (List a) => (a[0] == null) ? 0 : a[0].length); 535 | def("stringp", 1, (List a) => (a[0] is String) ? true : null); 536 | def("numberp", 1, (List a) => isNumber(a[0]) ? true : null); 537 | def("eql", 2, (List a) { 538 | var x = a[0]; 539 | var y = a[1]; 540 | return (x == y) ? true : 541 | (isNumber(x) && isNumber(y) && compare(x, y) == 0) ? true : null; 542 | }); 543 | def("<", 2, (List a) => (compare(a[0], a[1]) < 0) ? true : null); 544 | def("%", 2, (List a) => remainder(a[0], a[1])); 545 | def("mod", 2, (List a) { 546 | var x = a[0]; 547 | var y = a[1]; 548 | var q = remainder(x, y); 549 | return (compare(multiply(x, y), 0) < 0) ? add(q, y) : q; 550 | }); 551 | 552 | def("+", -1, (List a) => foldl(0, a[0], (i, j) => add(i, j))); 553 | def("*", -1, (List a) => foldl(1, a[0], (i, j) => multiply(i, j))); 554 | def("-", -2, (List a) { 555 | var x = a[0]; 556 | Cell y = a[1]; 557 | return (y == null) ? -x : foldl(x, y, (i, j) => subtract(i, j)); 558 | }); 559 | def("/", -3, (List a) => 560 | foldl(divide(a[0], a[1]), a[2], (i, j) => divide(i, j))); 561 | 562 | def("truncate", -2, (List a) { 563 | var x = a[0]; 564 | Cell y = a[1]; 565 | return (y == null) ? quotient(x, 1) : 566 | (y.cdr == null) ? quotient(x, y.car) : 567 | throw "one or two arguments expected"; 568 | }); 569 | 570 | def("prin1", 1, (List a) { cout.write(str(a[0], true)); return a[0]; }); 571 | def("princ", 1, (List a) { cout.write(str(a[0], false)); return a[0]; }); 572 | def("terpri", 0, (List a) { cout.writeln(); return true; }); 573 | 574 | var gensymCounterSym = Sym("*gensym-counter*"); 575 | globals[gensymCounterSym] = 1; 576 | def("gensym", 0, (List a) { 577 | int i = globals[gensymCounterSym]; 578 | globals[gensymCounterSym] = i + 1; 579 | return new Sym.internal("G$i"); 580 | }); 581 | 582 | def("make-symbol", 1, (List a) => new Sym.internal(a[0])); 583 | def("intern", 1, (List a) => Sym(a[0])); 584 | def("symbol-name", 1, (List a) => (a[0] as Sym).name); 585 | 586 | def("apply", 2, (List a) => eval(Cell(a[0], mapcar(a[1], qqQuote)), null)); 587 | 588 | def("exit", 1, (List a) => exit(a[0])); 589 | def("dump", 0, (List a) => globals.keys.fold(null, (x, y) => Cell(y, x))); 590 | globals[Sym("*version*")] = 591 | Cell(2.000, Cell("Dart", Cell("Nukata Lisp", null))); 592 | // named after Tōkai-dō Mikawa-koku Nukata-gun (東海道 三河国 額田郡) 593 | } 594 | 595 | /// Defines a built-in function by giving a name, an arity, and a body. 596 | void def(String name, int carity, BuiltInFuncBody body) { 597 | globals[Sym(name)] = BuiltInFunc(name, carity, body); 598 | } 599 | 600 | /// Evaluates a Lisp expression in an environment. 601 | eval(x, Cell env) { 602 | try { 603 | for (;;) { 604 | if (x is Arg) { 605 | return x.getValue(env); 606 | } else if (x is Sym) { 607 | if (globals.containsKey(x)) 608 | return globals[x]; 609 | throw EvalException("void variable", x); 610 | } else if (x is Cell) { 611 | var fn = x.car; 612 | Cell arg = cdrCell(x); 613 | if (fn is Keyword) { 614 | if (fn == quoteSym) { 615 | if (arg != null && arg.cdr == null) 616 | return arg.car; 617 | throw EvalException("bad quote", x); 618 | } else if (fn == prognSym) { 619 | x = _evalProgN(arg, env); 620 | } else if (fn == condSym) { 621 | x = _evalCond(arg, env); 622 | } else if (fn == setqSym) { 623 | return _evalSetQ(arg, env); 624 | } else if (fn == lambdaSym) { 625 | return _compile(arg, env, Closure.make); 626 | } else if (fn == macroSym) { 627 | if (env != null) 628 | throw EvalException("nested macro", x); 629 | return _compile(arg, null, Macro.make); 630 | } else if (fn == quasiquoteSym) { 631 | if (arg != null && arg.cdr == null) 632 | x = qqExpand(arg.car); 633 | else 634 | throw EvalException("bad quasiquote", x); 635 | } else { 636 | throw EvalException("bad keyword", fn); 637 | } 638 | } else { // Application of a function 639 | // Expands fn = eval(fn, env) here on Sym for speed. 640 | if (fn is Sym) { 641 | fn = globals[fn]; 642 | if (fn == null) 643 | throw EvalException("undefined", x.car); 644 | } else { 645 | fn = eval(fn, env); 646 | } 647 | 648 | if (fn is Closure) { 649 | env = fn.makeEnv(this, arg, env); 650 | x = _evalProgN(fn.body, env); 651 | } else if (fn is Macro) { 652 | x = fn.expandWith(this, arg); 653 | } else if (fn is BuiltInFunc) { 654 | return fn.evalWith(this, arg, env); 655 | } else { 656 | throw EvalException("not applicable", fn); 657 | } 658 | } 659 | } else if (x is Lambda) { 660 | return new Closure.from(x, env); 661 | } else { 662 | return x; // numbers, strings, null etc. 663 | } 664 | } 665 | } on EvalException catch (ex) { 666 | if (ex.trace.length < 10) 667 | ex.trace.add(str(x)); 668 | throw ex; 669 | } 670 | } 671 | 672 | /// (progn E1 E2.. En) => Evaluates E1, E2, .. except for En and returns it. 673 | _evalProgN(Cell j, Cell env) { 674 | if (j == null) 675 | return null; 676 | for (;;) { 677 | var x = j.car; 678 | j = cdrCell(j); 679 | if (j == null) 680 | return x; // The tail expression will be evaluated at the caller. 681 | eval(x, env); 682 | } 683 | } 684 | 685 | /// Evaluates a conditional expression and returns the selection unevaluated. 686 | _evalCond(Cell j, Cell env) { 687 | for (; j != null; j = cdrCell(j)) { 688 | var clause = j.car; 689 | if (clause is Cell) { 690 | var result = eval(clause.car, env); 691 | if (result != null) { // If the condition holds 692 | Cell body = cdrCell(clause); 693 | if (body == null) 694 | return qqQuote(result); 695 | else 696 | return _evalProgN(body, env); 697 | } 698 | } else if (clause != null) { 699 | throw EvalException("cond test expected", clause); 700 | } 701 | } 702 | return null; // No clause holds. 703 | } 704 | 705 | /// (setq V1 E1 ..) => Evaluates Ei and assigns it to Vi; returns the last. 706 | _evalSetQ(Cell j, Cell env) { 707 | var result = null; 708 | for (; j != null; j = cdrCell(j)) { 709 | var lval = j.car; 710 | j = cdrCell(j); 711 | if (j == null) 712 | throw EvalException("right value expected", lval); 713 | result = eval(j.car, env); 714 | if (lval is Arg) 715 | lval.setValue(result, env); 716 | else if (lval is Sym && lval is! Keyword) 717 | globals[lval] = result; 718 | else 719 | throw NotVariableException(lval); 720 | } 721 | return result; 722 | } 723 | 724 | /// Compiles a Lisp list (macro ...) or (lambda ...). 725 | DefinedFunc _compile(Cell arg, Cell env, FuncFactory make) { 726 | if (arg == null) 727 | throw EvalException("arglist and body expected", arg); 728 | Map table = {}; 729 | bool hasRest = _makeArgTable(arg.car, table); 730 | int arity = table.length; 731 | Cell body = cdrCell(arg); 732 | body = _scanForArgs(body, table); 733 | body = _expandMacros(body, 20); // Expands ms statically up to 20 nestings. 734 | body = _compileInners(body); 735 | return make((hasRest) ? -arity : arity, body, env); 736 | } 737 | 738 | /// Expands macros and quasi-quotations in an expression. 739 | _expandMacros(j, int count) { 740 | if (count > 0 && j is Cell) { 741 | var k = j.car; 742 | if (k == quoteSym || k == lambdaSym || k == macroSym) { 743 | return j; 744 | } else if (k == quasiquoteSym) { 745 | Cell d = cdrCell(j); 746 | if (d != null && d.cdr == null) { 747 | var z = qqExpand(d.car); 748 | return _expandMacros(z, count); 749 | } 750 | throw EvalException("bad quasiquote", j); 751 | } else { 752 | if (k is Sym) 753 | k = globals[k]; // null if k does not have a value 754 | if (k is Macro) { 755 | Cell d = cdrCell(j); 756 | var z = k.expandWith(this, d); 757 | return _expandMacros(z, count - 1); 758 | } else { 759 | return mapcar(j, (x) => _expandMacros(x, count)); 760 | } 761 | } 762 | } else { 763 | return j; 764 | } 765 | } 766 | 767 | /// Replaces inner lambda-expressions with Lambda instances. 768 | _compileInners(j) { 769 | if (j is Cell) { 770 | var k = j.car; 771 | if (k == quoteSym) { 772 | return j; 773 | } else if (k == lambdaSym) { 774 | Cell d = cdrCell(j); 775 | return _compile(d, null, Lambda.make); 776 | } else if (k == macroSym) { 777 | throw EvalException("nested macro", j); 778 | } else { 779 | return mapcar(j, (x) => _compileInners(x)); 780 | } 781 | } else { 782 | return j; 783 | } 784 | } 785 | } 786 | 787 | //---------------------------------------------------------------------- 788 | 789 | /// Makes an argument-table; returns true if there is a rest argument. 790 | bool _makeArgTable(arg, Map table) { 791 | if (arg == null) { 792 | return false; 793 | } else if (arg is Cell) { 794 | int offset = 0; // offset value within the call-frame 795 | bool hasRest = false; 796 | for (; arg != null; arg = cdrCell(arg)) { 797 | var j = arg.car; 798 | if (hasRest) 799 | throw EvalException("2nd rest", j); 800 | if (j == restSym) { // &rest var 801 | arg = cdrCell(arg); 802 | if (arg == null) 803 | throw NotVariableException(arg); 804 | j = arg.car; 805 | if (j == restSym) 806 | throw NotVariableException(j); 807 | hasRest = true; 808 | } 809 | Sym sym = 810 | (j is Sym) ? j : 811 | (j is Arg) ? j.symbol : throw NotVariableException(j); 812 | if (table.containsKey(sym)) 813 | throw EvalException("duplicated argument name", sym); 814 | table[sym] = Arg(0, offset, sym); 815 | offset++; 816 | } 817 | return hasRest; 818 | } else { 819 | throw EvalException("arglist expected", arg); 820 | } 821 | } 822 | 823 | /// Scans [j] for formal arguments in [table] and replaces them with Args. 824 | /// And scans [j] for free Args not in [table] and promotes their levels. 825 | _scanForArgs(j, Map table) { 826 | if (j is Sym) { 827 | return table[j] ?? j; 828 | } else if (j is Arg) { 829 | return table[j.symbol] ?? Arg(j.level + 1, j.offset, j.symbol); 830 | } else if (j is Cell) { 831 | if (j.car == quoteSym) { 832 | return j; 833 | } else if (j.car == quasiquoteSym) { 834 | return Cell(quasiquoteSym, _scanForQQ(j.cdr, table, 0)); 835 | } else { 836 | return mapcar(j, (x) => _scanForArgs(x, table)); 837 | } 838 | } else { 839 | return j; 840 | } 841 | } 842 | 843 | /// Scans for quasi-quotes. 844 | /// And does [_scanForArgs] them depending on the nesting level. 845 | _scanForQQ(j, Map table, int level) { 846 | if (j is Cell) { 847 | var k = j.car; 848 | if (k == quasiquoteSym) { 849 | return Cell(k, _scanForQQ(j.cdr, table, level + 1)); 850 | } else if (k == unquoteSym || k == unquoteSplicingSym) { 851 | var d = (level == 0) ? _scanForArgs(j.cdr, table) : 852 | _scanForQQ(j.cdr, table, level - 1); 853 | if (identical(d, j.cdr)) 854 | return j; 855 | return Cell(k, d); 856 | } else { 857 | return mapcar(j, (x) => _scanForQQ(x, table, level)); 858 | } 859 | } else { 860 | return j; 861 | } 862 | } 863 | 864 | /// Gets cdr of list [x] as a Cell or null. 865 | Cell cdrCell(Cell x) { 866 | var k = x.cdr; 867 | if (k is Cell) 868 | return k; 869 | else if (k == null) 870 | return null; 871 | else 872 | throw EvalException("proper list expected", x); 873 | } 874 | 875 | //---------------------------------------------------------------------- 876 | // Quasi-Quotation 877 | 878 | /// Expands [x] of any quasi-quotation `x into the equivalent S-expression. 879 | qqExpand(x) { 880 | return _qqExpand0(x, 0); // Begins with the nesting level 0. 881 | } 882 | 883 | _qqExpand0(x, int level) { 884 | if (x is Cell) { 885 | if (x.car == unquoteSym) { // ,a 886 | if (level == 0) 887 | return x.cdr.car; // ,a => a 888 | } 889 | Cell t = _qqExpand1(x, level); 890 | if (t.car is Cell && t.cdr == null) { 891 | Cell k = t.car; 892 | if (k.car == listSym || k.car == consSym) 893 | return k; 894 | } 895 | return Cell(appendSym, t); 896 | } else { 897 | return qqQuote(x); 898 | } 899 | } 900 | 901 | /// Quotes [x] so that the result evaluates to [x]. 902 | qqQuote(x) => 903 | (x is Sym || x is Cell) ? Cell(quoteSym, Cell(x, null)) : x; 904 | 905 | // Expands [x] of `x so that the result can be used as an argument of append. 906 | // Example 1: (,a b) => h=(list a) t=((list 'b)) => ((list a 'b)) 907 | // Example 2: (,a ,@(cons 2 3)) => h=(list a) t=((cons 2 3)) 908 | // => ((cons a (cons 2 3))) 909 | Cell _qqExpand1(x, int level) { 910 | if (x is Cell) { 911 | if (x.car == unquoteSym) { // ,a 912 | if (level == 0) 913 | return x.cdr; // ,a => (a) 914 | level--; 915 | } else if (x.car == quasiquoteSym) { // `a 916 | level++; 917 | } 918 | var h = _qqExpand2(x.car, level); 919 | Cell t = _qqExpand1(x.cdr, level); // != null 920 | if (t.car == null && t.cdr == null) { 921 | return Cell(h, null); 922 | } else if (h is Cell) { 923 | if (h.car == listSym) { 924 | if (t.car is Cell) { 925 | Cell tcar = t.car; 926 | if (tcar.car == listSym) { 927 | var hh = _qqConcat(h, tcar.cdr); 928 | return Cell(hh, t.cdr); 929 | } 930 | } 931 | if (h.cdr is Cell) { 932 | var hh = _qqConsCons(h.cdr, t.car); 933 | return Cell(hh, t.cdr); 934 | } 935 | } 936 | } 937 | return Cell(h, t); 938 | } else { 939 | return Cell(qqQuote(x), null); 940 | } 941 | } 942 | 943 | // (1 2), (3 4) => (1 2 3 4) 944 | _qqConcat(Cell x, Object y) => 945 | (x == null) ? y : Cell(x.car, _qqConcat(x.cdr, y)); 946 | 947 | // (1 2 3), "a" => (cons 1 (cons 2 (cons 3 "a"))) 948 | _qqConsCons(Cell x, Object y) => 949 | (x == null) ? y : 950 | Cell(consSym, Cell(x.car, Cell(_qqConsCons(x.cdr, y), null))); 951 | 952 | // Expands [y] = x.car of `x so that result can be used as an arg of append. 953 | // Example: ,a => (list a); ,@(foo 1 2) => (foo 1 2); b => (list 'b) 954 | _qqExpand2(y, int level) { 955 | if (y is Cell) { 956 | if (y.car == unquoteSym) { // ,a 957 | if (level == 0) 958 | return Cell(listSym, y.cdr); // ,a => (list a) 959 | level--; 960 | } else if (y.car == unquoteSplicingSym) { // ,@a 961 | if (level == 0) 962 | return y.cdr.car; // ,@a => a 963 | level--; 964 | } else if (y.car == quasiquoteSym) { // `a 965 | level++; 966 | } 967 | } 968 | return Cell(listSym, Cell(_qqExpand0(y, level), null)); 969 | } 970 | 971 | //---------------------------------------------------------------------- 972 | 973 | /// Reader of Lisp expressions 974 | class Reader { 975 | final StreamIterator _rf; 976 | var _token; 977 | Iterator _tokens = [].iterator; 978 | int _lineNo = 0; 979 | bool _erred = false; 980 | 981 | /// Constructs a Reader which will read Lisp expressions from a given arg. 982 | Reader(this._rf); 983 | 984 | /// Reads a Lisp expression; returns #EOF if the input runs out normally. 985 | Future read() async { 986 | try { 987 | await _readToken(); 988 | return await _parseExpression(); 989 | } on FormatException catch (ex) { 990 | _erred = true; 991 | throw EvalException("syntax error", 992 | "${ex.message} -- $_lineNo: ${_rf.current}", false); 993 | } 994 | } 995 | 996 | Future _parseExpression() async { 997 | if (_token == leftParenSym) { // (a b c) 998 | await _readToken(); 999 | return await _parseListBody(); 1000 | } else if (_token == singleQuoteSym) { // 'a => (quote a) 1001 | await _readToken(); 1002 | return Cell(quoteSym, Cell(await _parseExpression(), null)); 1003 | } else if (_token == backQuoteSym) { // `a => (quasiquote a) 1004 | await _readToken(); 1005 | return Cell(quasiquoteSym, Cell(await _parseExpression(), null)); 1006 | } else if (_token == commaSym) { // ,a => (unquote a) 1007 | await _readToken(); 1008 | return Cell(unquoteSym, Cell(await _parseExpression(), null)); 1009 | } else if (_token == commaAtSym) { // ,@a => (unquote-splicing a) 1010 | await _readToken(); 1011 | return Cell(unquoteSplicingSym, Cell(await _parseExpression(), null)); 1012 | } else if (_token == dotSym || _token == rightParenSym) { 1013 | throw FormatException('unexpected "$_token"'); 1014 | } else { 1015 | return _token; 1016 | } 1017 | } 1018 | 1019 | Future _parseListBody() async { 1020 | if (_token == #EOF) { 1021 | throw FormatException("unexpected EOF"); 1022 | } else if (_token == rightParenSym) { 1023 | return null; 1024 | } else { 1025 | var e1 = await _parseExpression(); 1026 | await _readToken(); 1027 | var e2; 1028 | if (_token == dotSym) { // (a . b) 1029 | await _readToken(); 1030 | e2 = await _parseExpression(); 1031 | await _readToken(); 1032 | if (_token != rightParenSym) 1033 | throw FormatException('")" expected: $_token'); 1034 | } else { 1035 | e2 = await _parseListBody(); 1036 | } 1037 | return Cell(e1, e2); 1038 | } 1039 | } 1040 | 1041 | /// Reads the next token and sets it to [_token]. 1042 | Future _readToken() async { 1043 | while (! _tokens.moveNext() || _erred) { // line ends or erred last time 1044 | _erred = false; 1045 | _lineNo++; 1046 | if (! await _rf.moveNext()) { 1047 | _token = #EOF; 1048 | return; 1049 | } 1050 | _tokens = _tokenPat.allMatches(_rf.current) 1051 | .map((Match m) => m[1]) 1052 | .where((String s) => s != null) 1053 | .iterator; 1054 | } 1055 | _token = _tokens.current; 1056 | if (_token[0] == '"') { 1057 | String s = _token; 1058 | int n = s.length - 1; 1059 | if (n < 1 || s[n] != '"') 1060 | throw FormatException("bad string: '$s'"); 1061 | s = s.substring(1, n); 1062 | s = s.replaceAllMapped(_escapePat, (Match m) { 1063 | String key = m[1]; 1064 | return _escapes[key] ?? "\\$key"; 1065 | }); 1066 | _token = s; 1067 | return; 1068 | } 1069 | var n = tryParse(_token); 1070 | if (n != null) { 1071 | _token = n; 1072 | } else if (_token == "nil") { 1073 | _token = null; 1074 | } else if (_token == "t") { 1075 | _token = true; 1076 | } else { 1077 | _token = Sym(_token); 1078 | } 1079 | } 1080 | 1081 | /// Regular expression to split a line into Lisp tokens 1082 | static final _tokenPat = 1083 | RegExp('\\s+|;.*\$|("(\\\\.?|.)*?"|,@?|[^()\'`~"; \t]+|.)'); 1084 | 1085 | /// Regular expression to take an escape sequence out of a string 1086 | static final _escapePat = RegExp(r'\\(.)'); 1087 | 1088 | /// Mapping from a character of escape sequence to its string value 1089 | static final Map _escapes = { 1090 | "\\": "\\", 1091 | '"': '"', 1092 | "n": "\n", "r": "\r", "f": "\f", "b": "\b", "t": "\t", "v": "\v" 1093 | }; 1094 | } 1095 | 1096 | //---------------------------------------------------------------------- 1097 | 1098 | /// Mapping from a quote symbol to its string representation 1099 | final Map _quotes = { 1100 | quoteSym: "'", quasiquoteSym: "`", unquoteSym: ",", unquoteSplicingSym: ",@" 1101 | }; 1102 | 1103 | /// Makes a string representation of a Lisp expression 1104 | String str(x, [bool quoteString=true, int count, Set printed]) { 1105 | if (x == null) { 1106 | return "nil"; 1107 | } else if (x == true) { 1108 | return "t"; 1109 | } else if (x is Cell) { 1110 | if (_quotes.containsKey(x.car) && x.cdr is Cell) { 1111 | if (x.cdr.cdr == null) 1112 | return _quotes[x.car] + str(x.cdr.car, true, count, printed); 1113 | } 1114 | return "(" + _strListBody(x, count, printed) + ")"; 1115 | } else if (x is String) { 1116 | if (! quoteString) 1117 | return x; 1118 | var bf = StringBuffer('"'); 1119 | for (int ch in x.runes) 1120 | switch (ch) { 1121 | case 0x08: bf.write(r"\b"); break; 1122 | case 0x09: bf.write(r"\t"); break; 1123 | case 0x0A: bf.write(r"\n"); break; 1124 | case 0x0B: bf.write(r"\v"); break; 1125 | case 0x0C: bf.write(r"\f"); break; 1126 | case 0x0D: bf.write(r"\r"); break; 1127 | case 0x22: bf.write(r'\"'); break; 1128 | case 0x5C: bf.write(r"\\"); break; 1129 | default: bf.writeCharCode(ch); break; 1130 | } 1131 | bf.write('"'); 1132 | return bf.toString(); 1133 | } else if (x is List) { 1134 | var s = x.map((e) => str(e, true, count, printed)).join(", "); 1135 | return "[$s]"; 1136 | } else if (x is Sym) { 1137 | if (x.isInterned) 1138 | return x.name; 1139 | return "#:$x"; 1140 | } else { 1141 | return "$x"; 1142 | } 1143 | } 1144 | 1145 | /// Makes a string representation of a list omitting its "(" and ")". 1146 | String _strListBody(Cell x, int count, Set printed) { 1147 | printed ??= Set(); 1148 | count ??= 4; // threshold of ellipsis for circular lists 1149 | var s = List(); 1150 | var y; 1151 | for (y = x; y is Cell; y = y.cdr) { 1152 | if (printed.add(y)) { 1153 | count = 4; 1154 | } else { 1155 | count--; 1156 | if (count < 0) { 1157 | s.add("..."); // an ellipsis for a circular list 1158 | return s.join(" "); 1159 | } 1160 | } 1161 | s.add(str(y.car, true, count, printed)); 1162 | } 1163 | if (y != null) { 1164 | s.add("."); 1165 | s.add(str(y, true, count, printed)); 1166 | } 1167 | for (y = x; y is Cell; y = y.cdr) 1168 | printed.remove(y); 1169 | return s.join(" "); 1170 | } 1171 | 1172 | //---------------------------------------------------------------------- 1173 | 1174 | /// Runs REPL (Read-Eval-Print Loop). 1175 | Future run(Interp interp, Stream input) async { 1176 | bool interactive = (input == null); 1177 | input ??= stdin.transform(const Utf8Codec().decoder); 1178 | input = input.transform(const LineSplitter()); 1179 | var lines = StreamIterator(input); 1180 | var reader = Reader(lines); 1181 | for (;;) { 1182 | if (interactive) { 1183 | stdout.write("> "); 1184 | try { 1185 | var sExp = await reader.read(); 1186 | if (sExp == #EOF) 1187 | return; 1188 | var x = interp.eval(sExp, null); 1189 | print(str(x)); 1190 | } on Exception catch (ex) { 1191 | print(ex); 1192 | } 1193 | } else { 1194 | var sExp = await reader.read(); 1195 | if (sExp == #EOF) 1196 | return; 1197 | interp.eval(sExp, null); 1198 | } 1199 | } 1200 | } 1201 | 1202 | // Keywords 1203 | final Sym condSym = Keyword("cond"); 1204 | final Sym lambdaSym = Keyword("lambda"); 1205 | final Sym macroSym = Keyword("macro"); 1206 | final Sym prognSym = Keyword("progn"); 1207 | final Sym quasiquoteSym = Keyword("quasiquote"); 1208 | final Sym quoteSym = Keyword("quote"); 1209 | final Sym setqSym = Keyword("setq"); 1210 | 1211 | /// Makes a Lisp interpreter initialized with [prelude]. 1212 | Future makeInterp() async { 1213 | // Dart initializes static variables lazily. Therefore, all keywords are 1214 | // referred explicitly here so that they are initialized as keywords 1215 | // before any occurrences of symbols of their names. 1216 | [condSym, lambdaSym, macroSym, prognSym, quasiquoteSym, quoteSym, setqSym]; 1217 | 1218 | var interp = Interp(); 1219 | Future fs = new Future.value(prelude); 1220 | Stream ss = new Stream.fromFuture(fs); 1221 | await run(interp, ss); 1222 | return interp; 1223 | } 1224 | 1225 | /// Runs each arg as a Lisp script in order. 1226 | /// Runs interactively for no arg or -. 1227 | main(List args) async { 1228 | try { 1229 | Interp interp = await makeInterp(); 1230 | for (String fileName in (args.isEmpty) ? ["-"] : args) { 1231 | if (fileName == "-") { 1232 | await run(interp, null); 1233 | print("Goodbye"); 1234 | } else { 1235 | var file = File(fileName); 1236 | Stream> bytes = file.openRead(); 1237 | Stream input = bytes.transform(const Utf8Codec().decoder); 1238 | await run(interp, input); 1239 | } 1240 | } 1241 | exit(0); 1242 | } on Exception catch (ex) { 1243 | print(ex); 1244 | exit(1); 1245 | } 1246 | } 1247 | 1248 | /// Lisp initialization script 1249 | const String prelude = """ 1250 | (setq defmacro 1251 | (macro (name args &rest body) 1252 | `(progn (setq ,name (macro ,args ,@body)) 1253 | ',name))) 1254 | 1255 | (defmacro defun (name args &rest body) 1256 | `(progn (setq ,name (lambda ,args ,@body)) 1257 | ',name)) 1258 | 1259 | (defun caar (x) (car (car x))) 1260 | (defun cadr (x) (car (cdr x))) 1261 | (defun cdar (x) (cdr (car x))) 1262 | (defun cddr (x) (cdr (cdr x))) 1263 | (defun caaar (x) (car (car (car x)))) 1264 | (defun caadr (x) (car (car (cdr x)))) 1265 | (defun cadar (x) (car (cdr (car x)))) 1266 | (defun caddr (x) (car (cdr (cdr x)))) 1267 | (defun cdaar (x) (cdr (car (car x)))) 1268 | (defun cdadr (x) (cdr (car (cdr x)))) 1269 | (defun cddar (x) (cdr (cdr (car x)))) 1270 | (defun cdddr (x) (cdr (cdr (cdr x)))) 1271 | (defun not (x) (eq x nil)) 1272 | (defun consp (x) (not (atom x))) 1273 | (defun print (x) (prin1 x) (terpri) x) 1274 | (defun identity (x) x) 1275 | 1276 | (setq 1277 | = eql 1278 | rem % 1279 | null not 1280 | setcar rplaca 1281 | setcdr rplacd) 1282 | 1283 | (defun > (x y) (< y x)) 1284 | (defun >= (x y) (not (< x y))) 1285 | (defun <= (x y) (not (< y x))) 1286 | (defun /= (x y) (not (= x y))) 1287 | 1288 | (defun equal (x y) 1289 | (cond ((atom x) (eql x y)) 1290 | ((atom y) nil) 1291 | ((equal (car x) (car y)) (equal (cdr x) (cdr y))))) 1292 | 1293 | (defmacro if (test then &rest else) 1294 | `(cond (,test ,then) 1295 | ,@(cond (else `((t ,@else)))))) 1296 | 1297 | (defmacro when (test &rest body) 1298 | `(cond (,test ,@body))) 1299 | 1300 | (defmacro let (args &rest body) 1301 | ((lambda (vars vals) 1302 | (defun vars (x) 1303 | (cond (x (cons (if (atom (car x)) 1304 | (car x) 1305 | (caar x)) 1306 | (vars (cdr x)))))) 1307 | (defun vals (x) 1308 | (cond (x (cons (if (atom (car x)) 1309 | nil 1310 | (cadar x)) 1311 | (vals (cdr x)))))) 1312 | `((lambda ,(vars args) ,@body) ,@(vals args))) 1313 | nil nil)) 1314 | 1315 | (defmacro letrec (args &rest body) ; (letrec ((v e) ...) body...) 1316 | (let (vars setqs) 1317 | (defun vars (x) 1318 | (cond (x (cons (caar x) 1319 | (vars (cdr x)))))) 1320 | (defun sets (x) 1321 | (cond (x (cons `(setq ,(caar x) ,(cadar x)) 1322 | (sets (cdr x)))))) 1323 | `(let ,(vars args) ,@(sets args) ,@body))) 1324 | 1325 | (defun _append (x y) 1326 | (if (null x) 1327 | y 1328 | (cons (car x) (_append (cdr x) y)))) 1329 | (defmacro append (x &rest y) 1330 | (if (null y) 1331 | x 1332 | `(_append ,x (append ,@y)))) 1333 | 1334 | (defmacro and (x &rest y) 1335 | (if (null y) 1336 | x 1337 | `(cond (,x (and ,@y))))) 1338 | 1339 | (defun mapcar (f x) 1340 | (and x (cons (f (car x)) (mapcar f (cdr x))))) 1341 | 1342 | (defmacro or (x &rest y) 1343 | (if (null y) 1344 | x 1345 | `(cond (,x) 1346 | ((or ,@y))))) 1347 | 1348 | (defun listp (x) 1349 | (or (null x) (consp x))) ; NB (listp (lambda (x) (+ x 1))) => nil 1350 | 1351 | (defun memq (key x) 1352 | (cond ((null x) nil) 1353 | ((eq key (car x)) x) 1354 | (t (memq key (cdr x))))) 1355 | 1356 | (defun member (key x) 1357 | (cond ((null x) nil) 1358 | ((equal key (car x)) x) 1359 | (t (member key (cdr x))))) 1360 | 1361 | (defun assq (key alist) 1362 | (cond (alist (let ((e (car alist))) 1363 | (if (and (consp e) (eq key (car e))) 1364 | e 1365 | (assq key (cdr alist))))))) 1366 | 1367 | (defun assoc (key alist) 1368 | (cond (alist (let ((e (car alist))) 1369 | (if (and (consp e) (equal key (car e))) 1370 | e 1371 | (assoc key (cdr alist))))))) 1372 | 1373 | (defun _nreverse (x prev) 1374 | (let ((next (cdr x))) 1375 | (setcdr x prev) 1376 | (if (null next) 1377 | x 1378 | (_nreverse next x)))) 1379 | (defun nreverse (list) ; (nreverse '(a b c d)) => (d c b a) 1380 | (cond (list (_nreverse list nil)))) 1381 | 1382 | (defun last (list) 1383 | (if (atom (cdr list)) 1384 | list 1385 | (last (cdr list)))) 1386 | 1387 | (defun nconc (&rest lists) 1388 | (if (null (cdr lists)) 1389 | (car lists) 1390 | (if (null (car lists)) 1391 | (apply nconc (cdr lists)) 1392 | (setcdr (last (car lists)) 1393 | (apply nconc (cdr lists))) 1394 | (car lists)))) 1395 | 1396 | (defmacro while (test &rest body) 1397 | (let ((loop (gensym))) 1398 | `(letrec ((,loop (lambda () (cond (,test ,@body (,loop)))))) 1399 | (,loop)))) 1400 | 1401 | (defmacro dolist (spec &rest body) ; (dolist (name list [result]) body...) 1402 | (let ((name (car spec)) 1403 | (list (gensym))) 1404 | `(let (,name 1405 | (,list ,(cadr spec))) 1406 | (while ,list 1407 | (setq ,name (car ,list)) 1408 | ,@body 1409 | (setq ,list (cdr ,list))) 1410 | ,@(if (cddr spec) 1411 | `((setq ,name nil) 1412 | ,(caddr spec)))))) 1413 | 1414 | (defmacro dotimes (spec &rest body) ; (dotimes (name count [result]) body...) 1415 | (let ((name (car spec)) 1416 | (count (gensym))) 1417 | `(let ((,name 0) 1418 | (,count ,(cadr spec))) 1419 | (while (< ,name ,count) 1420 | ,@body 1421 | (setq ,name (+ ,name 1))) 1422 | ,@(if (cddr spec) 1423 | `(,(caddr spec)))))) 1424 | """; 1425 | 1426 | /* 1427 | Copyright (c) 2015, 2016 OKI Software Co., Ltd. 1428 | Copyright (c) 2018, 2019 SUZUKI Hisao 1429 | 1430 | Permission is hereby granted, free of charge, to any person obtaining a 1431 | copy of this software and associated documentation files (the "Software"), 1432 | to deal in the Software without restriction, including without limitation 1433 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 1434 | and/or sell copies of the Software, and to permit persons to whom the 1435 | Software is furnished to do so, subject to the following conditions: 1436 | 1437 | The above copyright notice and this permission notice shall be included in 1438 | all copies or substantial portions of the Software. 1439 | 1440 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 1441 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 1442 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 1443 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 1444 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 1445 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 1446 | DEALINGS IN THE SOFTWARE. 1447 | */ --------------------------------------------------------------------------------